Endpoint Reference
Full reference for every REST endpoint under /api/v1, plus the
separate Jivo-migration compatibility routes. This page mirrors the ACTUAL
committed route/controller code, not just the original design proposal -- see
the notes under each table for anywhere behavior is still evolving.
Conventions
- Base URL:
https://<host>/api/v1. See Quickstart for a first request, Authentication & Scopes for tokens and scopes.
- Universal codes: every endpoint below can additionally return
401 (missing/invalid/expired token), 403 (token lacks the required scope), or 429 (rate limited) from the shared authentication/rate-limit layer -- these are omitted from the per-endpoint "Codes" columns below to avoid repeating them 26 times; only the codes the endpoint's own logic produces are listed there.
- Idempotency: every write endpoint below requires an
X-Smartbtn-Idempotency-Key header, except DELETE /v1/webhook-subscriptions/{id} (deletion is naturally idempotent) and POST /v1/ai/replies (dedup instead uses the single-use causation_id). A missing header returns 400; reusing a key already seen in the last 24 hours returns 409.
- Pagination: list endpoints accept
?cursor=&limit= (limit capped at 100, default 20) and return {"data": [...], "next_cursor": "..." | null}.
- Errors: almost every error response is RFC-7807
application/problem+json ({type, title, status, detail, instance, errors?}) -- the one exception is noted under AI Replies below.
- Ids: chats, files, webhook subscriptions, and Jivo Bot-API chat ids are internal numeric ids; contacts use a 32-character opaque guid instead (never the internal numeric id) -- both are tenant-scoped, so an id belonging to a different account always 404s rather than leaking existence.
Chats & Messages
| Method | Path | Scope | Key parameters | Codes |
| GET | /v1/chats | chats:read | ?status=&channel=&cursor=&limit= | 200 |
| GET | /v1/chats/{id} | chats:read | — | 200/404 |
| POST | /v1/chats/{id}/close | chats:write | {reason?} | 200/400/404/409/422 |
| POST | /v1/chats/{id}/transfer | chats:write | {agent_id} (required; must belong to this account) | 202/400/404/409/422 |
| POST | /v1/chats/{id}/rate-request | chats:write | — (no body) | 202/400/404/409 |
| GET | /v1/chats/{id}/messages | messages:read | ?cursor=&limit= | 200/404 |
| POST | /v1/chats/{id}/messages | messages:write | {type: text|image|file|location|sticker|keyboard, body?, markdown?, file?, coords?, sticker?, keyboard?, origin?, agent_id?} | 202/400/404/409/422 |
| POST | /v1/chats/{id}/typing | messages:write | {state: start|stop} (server-throttled to ~1/5s per chat) | 202/400/404/409/422 |
| POST | /v1/chats/{id}/seen | messages:write | {message_id} | 200/400/404/409/422 |
transfer, rate-request, sending a message, and
typing hand off to the internal chat/WS layer rather than writing
synchronously, which is why they answer 202 (accepted) instead of
the resource-style 200/201 a plain CRUD call might
imply. close and seen are plain synchronous database
writes and answer 200 once the update has actually happened.
Contacts & Tags
| Method | Path | Scope | Key parameters | Codes |
| GET | /v1/contacts | contacts:read | ?query=&cursor=&limit= (query matches name/email/phone) | 200 |
| GET | /v1/contacts/{id} | contacts:read | — | 200/404 |
| PATCH | /v1/contacts/{id} | contacts:write | {name?, email?, phone?, attributes?} (merge-safe -- omitted fields are left untouched) | 200/400/404/409/422 |
| GET | /v1/tags | tags:read | — (distinct tag names in use across this account) | 200 |
| POST | /v1/contacts/{id}/tags | tags:write | {tags: [name, ...]} (re-posting an existing tag is a no-op, not a duplicate) | 200/400/404/409/422 |
Agents
| Method | Path | Scope | Key parameters | Codes |
| GET | /v1/agents | agents:read | ?online=1|0&cursor=&limit= | 200 |
| GET | /v1/agents/{id} | agents:read | — | 200/404 |
There is no write scope for agents in v1 -- both routes are read-only.
Files
| Method | Path | Scope | Key parameters | Codes |
| GET | /v1/files/{id} | files:read | — (redirects to the stored file) | 302/404 |
| POST | /v1/files | files:write | multipart file OR JSON {url} (exactly one; 10 MB cap either way) | 201/400/409/422/500 |
Fetch-by-url is guarded against SSRF (private/loopback/link-local ranges are
rejected, and the resolved IP is re-validated on the one redirect hop this
endpoint follows) -- a blocked or unreachable url is reported as 422,
not 500.
Stats
| Method | Path | Scope | Key parameters | Codes |
| GET | /v1/stats | stats:read | ?from=&to=&metric= (default window: trailing 30 days; metric is an optional comma-separated subset of chats_total, messages_total, avg_response_time_seconds, rating_average -- omitted means all four) | 200/422 |
Webhook Subscriptions
| Method | Path | Scope | Key parameters | Codes |
| GET | /v1/webhook-subscriptions | webhooks:read | ?cursor=&limit= (subscription secret is never included) | 200 |
| POST | /v1/webhook-subscriptions | webhooks:write | {url (https only, SSRF-checked), events: [...], secret?, is_jivo_compat?} -- secret is generated for you and returned once if omitted | 201/400/409/422 |
| DELETE | /v1/webhook-subscriptions/{id} | webhooks:write | — | 204/404 |
| POST | /v1/webhook-subscriptions/{id}/redeliver | webhooks:write | {delivery_id} | 202/400/404/409/422 |
events currently accepts one or more of the 7 frozen native event
names (see Webhooks for the full
catalog and payload shapes), or, when is_jivo_compat is true, one or
more of the 5 Jivo-compat event names instead. A 6th unique-constraint case is
also reported as 409: creating a second subscription for a url
already registered on this account.
AI Replies
| Method | Path | Scope | Key parameters | Codes |
| POST | /v1/ai/replies | responder:inject | {room_id, causation_id, content?, escalate?} -- async response to an earlier outbound AI-operator webhook call, matched by causation_id (single-use) | 202/403/404/409/422 |
This is the one endpoint on this page whose 422 response is NOT
RFC-7807 shaped -- it currently renders as the framework's default validation
error body ({"message": ..., "errors": {...}}) rather than
application/problem+json. Every other 422 on this page
comes from the shared problem-response builder. 403 here covers both
"missing scope" (shared middleware) and "causation_id belongs to a different
account" (endpoint-specific); 409 covers a malformed or mismatched
causation record.
Channels
| Method | Path | Scope | Key parameters | Codes |
| GET | /v1/channels/{id}/status | channels:read | — → {"online": 0|1} | 200/404 |
| POST | /v1/channels/{id}/messages | channels:write | {external_client_id, external_channel_id, message} -- inbound delivery from your custom channel into this account's chat | 202/400/404/409/422 |
Jivo-compat routes
The three routes below exist specifically to make migrating from an
existing Jivo bot/channel integration to Smartbtn a base-URL-and-token
swap rather than a rewrite. They accept Jivo's own verbatim wire shapes and
translate internally onto the native endpoints above -- they are not part of the
native /v1 resource surface, live under their own
/compat/jivo prefix, and are authenticated differently (a token or
secret embedded in the URL path, exactly like Jivo's own convention, rather than
an Authorization header).
| Method | Path | Auth | Key parameters | Codes |
| POST |
/compat/jivo/bot/{token} |
bot token in the path (same token store as a native bearer token) |
{message_type: BOT_MESSAGE|INVITE_AGENT|INIT_RATE, chat_id, message?}
— BOT_MESSAGE maps to sending a message as an
agent, INVITE_AGENT maps to a transfer (picks a random
online agent; responds 200 with an informational
status if none are online, which is expected, not an error),
INIT_RATE maps to a rate-request.
|
200/202/401/403/404/422 |
| POST |
/compat/jivo/chat/{secret} |
per-channel secret in the path |
{sender: {id}, recipient: {id}, message} -- text-only inbound message on the matching custom channel |
202/403/404/422 |
| GET |
/compat/jivo/chat/{secret}/status |
per-channel secret in the path |
— → bare 0 or 1 text body (NOT JSON -- this one route intentionally matches Jivo's own plain-text wire format instead of this API's usual application/json) |
200/403/404 |