Skip to content
Documentation menu

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).

Shell
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 with POST /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:

Shell
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 answer 404, other routes 403).
  • 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 answer 402.
  • 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 get 429 with Retry-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.
ScopeAllows
org:readRead the organization
members:readList members
websites:readRead websites and their widget settings
visitors:readVisitors, live visitors, journeys
conversations:readConversations, messages, search, agents
conversations:replySend messages and notes, change status, tags, take chats
conversations:manageAssign and transfer to anyone
teams:manageTeams and routing
canned:manageSaved replies
tags:manageTags
reports:readAnalytics

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.

HTTP
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
HTTP/1.1 400 Bad Request
x-request-id: 3f0c…

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": ["formData.email must be an email address"]
}
StatusMeaning
400Invalid request. Unknown fields are rejected too
401Missing, invalid or expired access token or API key, or the session was revoked
402The plan does not include this (for example the API on Free)
403Your role (or the API key’s scopes) lacks the permission
404Not found, or not a member of the organization
409Conflict: for example a duplicate email or name, or writing to a closed conversation
410Invitation expired, revoked or already used
413Attachment too large (10 MB by default)
429Rate 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).

RouteLimit
Sign up10 per hour per IP
Log in30 per 15 minutes per IP, and 10 per 15 minutes per IP and email
Widget sessions30 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 write120 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.

AreaPathsCovers
Auth & sessions/auth/…, /users/meRegister, log in, refresh, sessions, password flows
Organizations/organizationsYour organizations, members, invitations, audit log
Websites/organizations/{id}/websitesWebsites, widget settings, site keys, identity secret
Conversations/organizations/{id}/conversationsInbox, messages, attachments, notes, tags, assignment and transfer
Search/organizations/{id}/search/messagesFull-text search over all messages
Visitors/organizations/{id}/visitorsLive visitors, profiles, journeys, proactive chat
Teams & routing/organizations/{id}/teams, /agents, /routingTeams, agent status, routing
Tags & saved replies/organizations/{id}/tags, /canned-responsesShared lists
API keys & webhooks/organizations/{id}/api-keys, /webhooksKeys, endpoints, delivery log
Health/health, /health/liveReadiness and liveness (public)

For live updates (new messages, typing, visitors coming and going), don’t poll: connect to the realtime API.