API overview
Every RolChat REST endpoint follows one contract: the same base URL, the same response envelope, the same pagination, the same error shape. Learn it once and every endpoint family in the endpoint reference reads the same way.
Base URL and versioning
Section titled “Base URL and versioning”https://api.rolchat.com/api/v1/The version is in the path. Adding optional fields, new endpoints or new query parameters with defaults is not a breaking change and does not bump the version. Breaking changes ship as v2, and deprecated v1 endpoints keep working for at least six months after they are announced (see Deprecation).
Authentication
Section titled “Authentication”| Method | Header | Used for |
|---|---|---|
| JWT bearer | Authorization: Bearer <access_token> |
Interactive sessions (the web app, mobile apps) |
| API key | Authorization: Bearer rcp_<secret> |
Machine-to-machine calls, plugin API keys |
| HMAC signature | X-RolChat-Signature: sha256=… |
Inbound webhooks from your systems, see Webhooks |
| None | - | Public widget endpoints (/widget/…, /forms/…), scoped by site and origin |
For integrations use an API key (a personal access token). Keys are created in Settings → Plugins → RolChat API keys, shown once and stored only as a hash. A key can never do more than the member who created it: its scopes are checked against that member’s current permissions on every call. See Authentication for JWT lifetimes, the X-Account-Id header and multi-company sessions.
Response envelope
Section titled “Response envelope”A single resource:
{ "data": { "id": "cnt_01HK…", "email": "ada@example.com" }, "meta": { "request_id": "req_01HK…" }}A list:
{ "data": [ { … }, { … } ], "meta": { "request_id": "req_01HK…", "count": 50, "next_cursor": "cur_01HK…", "prev_cursor": "cur_01HK…", "has_more": true }}Responses without a body (204 No Content after a delete) still carry the X-Request-Id header.
An error:
{ "errors": [ { "code": "validation_failed", "message": "Email must be valid.", "field": "email", "details": { "received": "not-an-email" } } ], "meta": { "request_id": "req_01HK…" }}code is machine-readable snake_case and is the value to branch on; message is English and meant for logs. field and details are present when they help and never contain personal data. The API never answers 200 with an errors array: if errors are present the status is 400 or higher. The full code list is on the Errors page.
Pagination
Section titled “Pagination”List endpoints that can grow use cursor pagination. Ask for the first page without a cursor, then pass back the next_cursor you received, verbatim:
GET /api/v1/contacts?limit=50GET /api/v1/contacts?limit=50&cursor=eyJrIjoi…limitdefaults to 50 and is capped at 200; larger values are rejected with400 validation_failed.- Cursors are opaque and expire after 24 hours (
400 validation_failedwithcode: cursor_expired). - Results are consistent against the snapshot taken on the first page: records deleted between pages disappear, new records appear only when you start again without a cursor.
- A
Linkheader withrel="next"andrel="prev"is also sent for clients that prefer it.
Idempotency
Section titled “Idempotency”Send an Idempotency-Key header (a UUID or ULID, up to 255 characters) with any POST, PATCH or DELETE:
- The first request with a key runs and its status and body are stored for 24 hours.
- A repeat with the same key and the same body returns the stored response without running side effects again.
- A repeat with the same key but a different body is rejected with
409 idempotency_key_conflict.
Use it for every call that creates something with side effects: sending a message, an e-mail or an SMS, placing a call, anything that bills. GET requests are always idempotent and ignore the header.
Rate limits
Section titled “Rate limits”Limits are counted per minute and keyed by the account, the API key or the user (by IP address on routes that need no sign-in). The default ceilings:
| Bucket | Default limit | Per minute |
|---|---|---|
| Requests without sign-in (by IP) | 60 | per IP |
| Signed-in user (dashboard) | 1 000 | per user |
| API key | 1 000 | per key |
| Public widget endpoints | 60 per visitor, and 600 per network address | per minute |
Sign-in (/auth/login) |
5 | per IP |
| Inbound webhooks | 300 | per minute |
| MCP server | 600 | per minute |
| OAuth endpoints | 600 | per minute |
The ceilings are the platform defaults and can be changed by the operator; your plan does not multiply them, and enterprise limits are agreed in your contract. Rate-limited endpoints return:
X-RateLimit-Limit: 1000X-RateLimit-Remaining: 543When you cross a limit the response is 429 with code: rate_limited in the errors array and a Retry-After header in seconds. Wait that long before the next call. Ten failed sign-ins lock the account for 30 minutes.
Sizes and timeouts
Section titled “Sizes and timeouts”| Category | Max request body | Server timeout |
|---|---|---|
| Default | 1 MB | 30 s |
| Message with attachments | 25 MB | 30 s |
| CSV import | 50 MB | returns 202 and runs in the background |
| Knowledge base images | 10 MB | 30 s |
| Bulk API | 10 MB | 30 s |
| Search | 1 MB | 10 s |
| AI requests | 1 MB | 60 s |
| Widget endpoints | 100 KB | 30 s |
Oversized bodies get 413 payload_too_large. A timed-out upstream answers 504 with code: upstream_timeout and a retry_after hint when a retry can help.
Content types, dates and IDs
Section titled “Content types, dates and IDs”- Requests and responses are
application/json; charset=utf-8. File uploads usemultipart/form-data; CSV exports returntext/csv. Invalid UTF-8 is rejected with400 validation_failed. - Timestamps are ISO-8601 in UTC with millisecond precision:
2026-04-20T14:23:45.123Z. Any ISO-8601 offset is accepted on input and normalised to UTC. - Identifiers are opaque ULIDs or UUIDs. There are no numeric IDs, and e-mail addresses or phone numbers never appear in a URL.
Tenant isolation and permissions
Section titled “Tenant isolation and permissions”Every authenticated request runs inside one account. A resource that belongs to another account, or to a site the caller’s role does not cover, answers 404 not_found rather than 403, so that the existence of records is never leaked. A valid token without the required permission (for example contacts.delete) answers 403 forbidden. Some permissions carry a scope suffix (conversations.read:own, :team, :all) that narrows what a list returns.
PATCH accepts only writable fields. id, account_id, created_at, updated_at and state fields such as resolved_at are read-only; sending one returns 400 validation_failed with details.forbidden_fields. State changes go through dedicated actions such as POST /tickets/:id/resolve.
Every write is recorded in the account audit log.
Observability headers
Section titled “Observability headers”Every response carries:
X-Request-Id: req_01HK…Log X-Request-Id. It is the first thing support asks for.
The browser dashboard origin is https://app.rolchat.com. Public widget endpoints allow any origin, or only the origins you verify for a site. Server-to-server calls with an API key are not subject to CORS.
Deprecation
Section titled “Deprecation”- The change is announced in the changelog and the affected endpoint starts sending
Deprecation: trueand aSunsetheader with the removal date. - Public endpoints keep working for at least six months after the announcement.
- Removal happens only in a new major version. Inside
v1changes are additive.
Health
Section titled “Health”Every service exposes /healthz (liveness) and /readyz (readiness).