Skip to main content

Erghi API Reference

Gateway Base URL (live today): https://api.staging.erghi.ai — staging. is currently the only live prefix platform-wide, not a separate sandbox; this is the real, production gateway. api.erghi.ai does not resolve yet. Gateway Base URL (local dev): http://localhost:5080
Conversation API (direct, dev only): http://localhost:5002
Content-Type: application/json

All production API traffic routes through the Gateway API (localhost:5080), which validates JWTs, enforces rate limits, and proxies requests to downstream services.

Authentication​

Endpoints marked Auth Required need a Bearer JWT token issued by GoTrue or generated via the Client Credentials flow.

Authorization: Bearer <your-jwt-token>

User Authentication (GoTrue)​

Obtain a token for a user:

POST /auth/v1/token?grant_type=password
Content-Type: application/json

{
"email": "agent@company.com",
"password": "your-password"
}

Machine-to-Machine Authentication (M2M)​

Obtain a token for a backend service or SDK using Client Credentials:

POST /api/v1/auth/token
Content-Type: application/json

{
"grant_type": "client_credentials",
"client_id": "your-client-id",
"client_secret": "your-client-secret"
}

Response 200 OK

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}

Client Credentials Management​

Admins can manage M2M keys via the API:

  • POST /api/v1/auth/keys - Generate a new Client ID and Client Secret pair.
  • GET /api/v1/auth/keys - List active client credentials for a workspace.
  • DELETE /api/v1/auth/keys/{id} - Revoke a credential pair.

Widget-initiated requests (creating conversations, sending messages) do not require a JWT — they authenticate with a widgetId.

Rate Limiting​

ScopeLimit
Authenticated users100 requests / minute
Widget endpoints60 requests / minute per IP

When exceeded: HTTP 429 Too Many Requests with Retry-After header.


Conversations​

POST /api/conversations — Widget Auth​

Start a new conversation. Called by the embedded widget when a visitor sends their first message.

Request body

{
"widgetId": "widget-uuid",
"visitorId": "visitor-uuid-or-null",
"metadata": {
"page": "/pricing",
"referrer": "google.com",
"userId": "optional-authenticated-user-id"
}
}

Response 201 Created

{
"id": "conv-uuid",
"workspaceId": "ws-uuid",
"widgetId": "widget-uuid",
"visitorId": "visitor-uuid",
"assignedAgentId": null,
"status": "open",
"channel": "widget",
"startedAt": "2025-01-15T10:30:00Z",
"closedAt": null,
"metadata": {
"page": "/pricing"
}
}

GET /api/conversations — Auth Required​

List all conversations in the authenticated workspace (agents and admins).

Query parameters

ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger20Results per page
statusstring—Filter: open, closed, pending
searchstring—Search by visitor ID

Response 200 OK — Paginated list of conversation objects


GET /api/conversations/{id} — Public​

Get a conversation by ID. Used by the widget to resume a session.

Response 200 OK — Full conversation object


POST /api/conversations/{id}/close — Auth Required​

Close a conversation and mark it as resolved.

Response 200 OK — Updated conversation with status: "closed" and closedAt timestamp


POST /api/conversations/{id}/assign — Auth Required​

Assign a conversation to a specific agent.

Request body

{
"agentId": "agent-user-uuid"
}

Response 200 OK — Updated conversation with assignedAgentId set


Messages​

POST /api/conversations/{id}/messages — Public / Auth Required​

Send a message in a conversation. Called by both visitors (no auth) and agents (auth required).

Request body

{
"content": "Hello, I have a question about your pricing.",
"senderType": "visitor",
"senderId": "visitor-uuid-or-agent-uuid"
}

senderType values: visitor, agent, ai

Response 201 Created

{
"id": "msg-uuid",
"conversationId": "conv-uuid",
"content": "Hello, I have a question about your pricing.",
"senderType": "visitor",
"senderId": "visitor-uuid",
"isRead": false,
"createdAt": "2025-01-15T10:30:05Z"
}

GET /api/conversations/{id}/messages — Public​

Get all messages in a conversation (paginated, chronological).

Query parameters

ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger50Messages per page

Response 200 OK

{
"data": [
{
"id": "msg-uuid",
"conversationId": "conv-uuid",
"content": "Hello!",
"senderType": "visitor",
"senderId": "visitor-uuid",
"isRead": true,
"createdAt": "2025-01-15T10:30:05Z"
}
],
"total": 12,
"page": 1,
"limit": 50
}

POST /api/conversations/{conversationId}/messages/{messageId}/read — Auth Required​

Mark a specific message as read (for read receipt tracking).

Response 200 OK — Updated message with isRead: true


Widgets​

POST /api/widgets — Auth Required (Admin)​

Create a new chat widget for the workspace.

Request body

{
"name": "Homepage Widget",
"welcomeMessage": "Hi! How can we help you today?",
"aiEnabled": true,
"themeColor": "#6366f1",
"autoAssign": true,
"offlineMessage": "We're offline right now. Leave a message and we'll respond ASAP."
}

Response 201 Created

{
"id": "widget-uuid",
"workspaceId": "ws-uuid",
"name": "Homepage Widget",
"welcomeMessage": "Hi! How can we help you today?",
"aiEnabled": true,
"themeColor": "#6366f1",
"autoAssign": true,
"offlineMessage": "We're offline right now...",
"embedCode": "<script src='https://chat.staging.erghi.ai/widget.js' data-widget-id='widget-uuid' async></script>",
"createdAt": "2025-01-15T09:00:00Z"
}

GET /api/widgets — Auth Required​

List all widgets in the workspace.

Response 200 OK — Array of widget objects


GET /api/widgets/{id} — Auth Required​

Get a single widget's configuration.

Response 200 OK — Full widget object


PUT /api/widgets/{id} — Auth Required (Admin)​

Update widget settings.

Request body — Same structure as POST; include only fields you want to change

Response 200 OK — Updated widget object


Admin Endpoints​

All /api/admin/ endpoints are scoped to the workspace in the caller's token and require the view_conversations permission (creating widgets also needs manage_widgets).

GET /api/admin/conversations/stats​

Workspace-wide conversation and message totals for the dashboard.

Response 200 OK

{
"totalConversations": 524,
"activeConversations": 18,
"queuedConversations": 3,
"resolvedConversations": 120,
"closedConversations": 383,
"totalMessages": 3205,
"todayMessages": 142,
"avgResponseTime": 1.8,
"conversationTrend": 12.5,
"messageTrend": 8.3
}
FieldNotes
activeConversationsOpen (AI answering) plus assigned to an agent
avgResponseTimeSeconds from a visitor's first message to the first reply (AI or agent), over up to 100 conversations from the last 30 days. null when there is nothing to measure — show "no data", not 0
conversationTrend, messageTrendPercent change, last 30 days vs the 30 before; 0 when the earlier period is empty

GET /api/admin/messages/stats​

Message counts over a window.

Query parameters

ParameterTypeDescription
daysintegerWindow size, default 30

Response 200 OK

{
"totalMessages": 3205,
"userMessages": 1610,
"agentMessages": 240,
"aiMessages": 1355,
"dailyStats": [{ "date": "2026-10-01T00:00:00Z", "count": 142 }]
}

GET /api/admin/conversations​

The inbox: workspace conversations, most recently started first.

Query parameters

ParameterTypeDescription
pageintegerPage number, default 1
limitintegerResults per page, default 50
statusstringOne status or a comma-separated list: open, queued, assigned, resolved, closed (e.g. open,assigned). Unknown values are ignored
searchstringCase-insensitive match on visitor name, visitor id, or any message content
widgetIdUUIDOnly conversations from this widget

Response 200 OK

{
"items": [
{
"id": "conv-uuid",
"userId": "visitor-uuid",
"widgetId": "widget-uuid",
"status": "open",
"channel": "web_widget",
"createdAt": "2026-10-02T09:41:19Z",
"updatedAt": "2026-10-02T09:41:50Z",
"messageCount": 4,
"lastMessage": { "content": "Thanks!", "timestamp": "2026-10-02T09:41:50Z" },
"isRead": false,
"visitorName": "Sara Mendes",
"visitorPhoneMasked": null,
"assignedAgentId": null
}
],
"statusCounts": { "open": 10, "queued": 1, "closed": 5 },
"totalCount": 16,
"page": 1,
"pageSize": 50,
"totalPages": 1
}

statusCounts applies the widgetId and search filters but not status, so each inbox tab can show how many it would contain. Statuses with no conversations are omitted.


GET /api/admin/widgets/stats​

Response 200 OK

{
"totalWidgets": 4,
"activeWidgets": 3
}

GET /api/admin/widgets​

List all workspace widgets (admin view with usage stats).


POST /api/admin/widgets​

Admin-level widget creation (same as POST /api/widgets with additional fields available).


Billing​

GET /api/v1/billing/plans — Public​

List available Erghi subscription plans.

Response 200 OK

[
{
"id": "plan-free",
"name": "Free",
"monthlyPrice": 0,
"annualPrice": 0,
"maxAgents": 1,
"maxConversations": 100,
"maxAiResponses": 0
},
{
"id": "plan-starter",
"name": "Starter",
"monthlyPrice": 29,
"annualPrice": 278,
"maxAgents": 3,
"maxConversations": 1000,
"maxAiResponses": 500
}
]

GET /api/v1/billing/current — Auth Required​

Get the current workspace plan and usage.

Response 200 OK

{
"planId": "plan-starter",
"planName": "Starter",
"agentsUsed": 2,
"agentsLimit": 3,
"conversationsUsed": 412,
"conversationsLimit": 1000,
"aiResponsesUsed": 87,
"aiResponsesLimit": 500,
"billingPeriodStart": "2025-01-01",
"billingPeriodEnd": "2025-01-31"
}

POST /api/v1/billing/checkout — Auth Required​

Create a Stripe checkout session.

Request body

{
"planId": "plan-growth",
"interval": "monthly"
}

Response 200 OK

{
"checkoutUrl": "https://checkout.stripe.com/pay/cs_..."
}

POST /api/v1/billing/portal — Auth Required​

Create a Stripe customer portal session.

Response 200 OK

{
"portalUrl": "https://billing.stripe.com/session/..."
}

POST /api/v1/billing/add-agent — Auth Required (Admin)​

Add an additional agent seat to the current plan (billed prorated).

Response 200 OK — Updated usage object


DELETE /api/v1/billing/remove-agent — Auth Required (Admin)​

Remove an agent seat from the plan.


GET /api/v1/billing/invoices — Auth Required​

Get invoice history for the workspace.

Response 200 OK — Array of invoice objects with amount, date, and PDF URL


POST /api/v1/billing/checkout — Auth Required (Admin)​

Start a checkout for a paid plan, using the workspace's configured payment provider — currently always Stripe (see Payment methods).

Request body

{
"planKey": "growth_monthly",
"successUrl": "https://your-site.example/billing/success",
"cancelUrl": "https://your-site.example/billing/plans",
"startTrial": false
}

Response 200 OK — a checkout URL to redirect the user to (a real Stripe Checkout session).

Paymob and Fawry are planned but not yet implemented — do not build against provider-specific checkout endpoints; there aren't any.


Real-Time: WebSocket / SignalR​

Erghi uses SignalR for real-time message delivery. Connect to the hub:

wss://api.staging.erghi.ai/hubs/chat?conversationId=<conv-id>&token=<jwt>

Events received from server:

EventPayloadDescription
ReceiveMessagemessage objectNew message in the conversation
AgentAssigned{ agentId, agentName }Conversation assigned to an agent
ConversationClosed{ conversationId }Conversation was closed
TypingStarted{ senderId, senderType }Sender is typing
TypingStopped{ senderId }Sender stopped typing

Events sent to server:

MethodParametersDescription
JoinConversationconversationIdSubscribe to a conversation's events
LeaveConversationconversationIdUnsubscribe
TypingStartconversationIdNotify others you're typing
TypingStopconversationIdNotify others you stopped

Errors​

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "Bad Request",
"status": 400,
"detail": "widgetId is required",
"traceId": "00-abc123-def456-00"
}
StatusMeaning
400Validation error
401Missing or invalid token
403Insufficient role (e.g., agent accessing admin route)
404Resource not found
429Rate limit exceeded
500Internal error