REST API overview
The ZebChat API is a JSON API over HTTPS. It is the same API the agent app and the widget use, so everything you can do in the app, you can do with the API.
- Base URL:
https://api.zebchat.com/api/v1 - Interactive reference (Swagger):
https://api.zebchat.com/api/docs - OpenAPI 3 document: download openapi.json (also served by the API at
/api/docs/openapi.json)
Authentication
The API accepts two kinds of bearer token: the access token of a signed-in person (below), and API keys for server-to-server integrations.
Send Authorization: Bearer <access token> with every request, except the few public routes (sign-up, log-in, password reset, health and the widget’s own routes).
curl -X POST https://api.zebchat.com/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email": "[email protected]", "password": "••••••••"}'
# → { "tokens": { "accessToken": "eyJ…", "refreshToken": "…", … }, "user": { … } }
curl https://api.zebchat.com/api/v1/auth/me \
-H 'Authorization: Bearer eyJ…'- The access token is valid for 15 minutes. When a request returns
401, get a new one withPOST /auth/refresh{refreshToken}and retry. - The refresh token lasts 30 days and can be used once: each refresh returns a new pair. Reusing an old refresh token ends the session, as a protection against stolen tokens. If several requests fail at once, refresh only once and let the others wait for it.
- Logging out (
POST /auth/logout) or revoking a session takes effect immediately.
API keys
Owners and admins create keys in Settings → API keys, or with POST /organizations/{id}/api-keys (Pro plan). The key is shown once; ZebChat stores only its prefix and a hash. Send it like a token:
curl https://api.zebchat.com/api/v1/organizations/org_…/api-keys -X POST -H 'Authorization: Bearer eyJ…' -H 'Content-Type: application/json' -d '{"name": "CRM sync", "scopes": ["conversations:read", "visitors:read"]}'
# → { "id": "key_…", "prefix": "zck_Ab3dEf9h", …, "key": "zck_Ab3dEf9h_…" } (shown only here)
curl https://api.zebchat.com/api/v1/organizations/org_…/conversations?status=all -H 'Authorization: Bearer zck_Ab3dEf9h_…'- A key works on
/organizations/{its organization}/…routes only (other organizations answer404, other routes403). - A key acts as the member who created it, with the permissions that member has now and that are in the key’s scopes. If the member loses a permission, so does the key; if they leave the organization, the key stops working (
401). - Revoked and expired keys answer
401. If the plan no longer includes the API, requests answer402. - Each key has its own rate limit (120 requests per minute unless set otherwise, up to 1,200). Responses carry
X-RateLimit-Limit; over the limit you get429withRetry-After. - Keys can’t manage members, organization settings, websites, billing, the audit log, API keys or webhooks, and can’t open realtime (Socket.IO) connections. Use webhooks for events.
| Scope | Allows |
|---|---|
org:read | Read the organization |
members:read | List members |
websites:read | Read websites and their widget settings |
visitors:read | Visitors, live visitors, journeys |
conversations:read | Conversations, messages, search, agents |
conversations:reply | Send messages and notes, change status, tags, take chats |
conversations:manage | Assign and transfer to anyone |
teams:manage | Teams and routing |
canned:manage | Saved replies |
tags:manage | Tags |
reports:read | Analytics |
Organizations and permissions
Almost everything belongs to an organization, so most paths start with /organizations/{organizationId}. You must be a member; for an organization you don’t belong to, the API answers 404, so ids can’t be probed.
What you may do depends on your role (owner, admin, supervisor, agent or viewer). Read the exact permissions of each membership from GET /auth/me rather than checking role names; a missing permission returns 403.
IDs
IDs are opaque strings with a prefix that tells you what they are, such as org_, usr_, web_, conv_, msg_, vis_, team_, tag_ or att_. Store them as strings and don’t parse them. A malformed id in the path returns 404; in a query string or body, 400.
Pagination
Lists that can grow use cursors. Pass limit and, for the next page, the nextCursor from the previous response as cursor (the audit log uses before). nextCursor is null on the last page.
GET /api/v1/organizations/org_…/conversations?status=all&limit=50
{
"data": [ { "id": "conv_…", "status": "open", … } ],
"nextCursor": "eyJ…" // null on the last page
}
GET /api/v1/organizations/org_…/conversations?status=all&limit=50&cursor=eyJ…Messages work differently because they are ordered by a per-conversation sequence number: GET …/messages?after=msg_… returns what came after a message (use it to catch up after a reconnect), ?before=msg_… scrolls back, and the response has hasMore instead of a cursor.
Errors
Errors share one shape. message is a string, or a list of strings when validation fails. Every response carries an x-request-id header: include it when you contact us.
HTTP/1.1 400 Bad Request
x-request-id: 3f0c…
{
"statusCode": 400,
"error": "Bad Request",
"message": ["formData.email must be an email address"]
}| Status | Meaning |
|---|---|
400 | Invalid request. Unknown fields are rejected too |
401 | Missing, invalid or expired access token or API key, or the session was revoked |
402 | The plan does not include this (for example the API on Free) |
403 | Your role (or the API key’s scopes) lacks the permission |
404 | Not found, or not a member of the organization |
409 | Conflict: for example a duplicate email or name, or writing to a closed conversation |
410 | Invitation expired, revoked or already used |
413 | Attachment too large (10 MB by default) |
429 | Rate limited: wait for the number of seconds in Retry-After |
Safe retries
Sending a message takes a clientMessageId that you generate (8–64 characters of letters, digits, _ and -). Retrying with the same id returns the original message instead of creating a duplicate.
Rate limits
Sensitive and public routes are rate limited. Over the limit, the API answers 429 with a Retry-After header (browsers can read it, and x-request-id, through CORS).
| Route | Limit |
|---|---|
| Sign up | 10 per hour per IP |
| Log in | 30 per 15 minutes per IP, and 10 per 15 minutes per IP and email |
| Widget sessions | 30 per minute and 300 per hour per IP; 3,000 per minute per site key |
| Starting a conversation (widget) | 60 per minute per IP, 20 per hour per visitor, 600 per minute per organization |
| setUser (widget) | 30 per minute per IP, 10 per minute per visitor |
| Any widget write | 120 per minute per visitor, 6,000 per minute per organization |
Requests with an API key also count against that key’s own limit (120 per minute by default).
Resources
An overview of what the API covers. The OpenAPI document has every route, parameter and response.
| Area | Paths | Covers |
|---|---|---|
| Auth & sessions | /auth/…, /users/me | Register, log in, refresh, sessions, password flows |
| Organizations | /organizations | Your organizations, members, invitations, audit log |
| Websites | /organizations/{id}/websites | Websites, widget settings, site keys, identity secret |
| Conversations | /organizations/{id}/conversations | Inbox, messages, attachments, notes, tags, assignment and transfer |
| Search | /organizations/{id}/search/messages | Full-text search over all messages |
| Visitors | /organizations/{id}/visitors | Live visitors, profiles, journeys, proactive chat |
| Teams & routing | /organizations/{id}/teams, /agents, /routing | Teams, agent status, routing |
| Tags & saved replies | /organizations/{id}/tags, /canned-responses | Shared lists |
| API keys & webhooks | /organizations/{id}/api-keys, /webhooks | Keys, endpoints, delivery log |
| Health | /health, /health/live | Readiness and liveness (public) |
For live updates (new messages, typing, visitors coming and going), don’t poll: connect to the realtime API.