Every REST call follows the same few conventions. This page is the reference for them; Quickstart shows them working.
Hosts and regions
| Workspace | API host | Base URL |
|---|---|---|
| Default | https://api.cx.na6iq.com | https://api.cx.na6iq.com/api/v1 |
| Saudi region | https://api.sa.na6iq.com | https://api.sa.na6iq.com/api/v1 |
Use the host of the region your workspace lives in, and sign in on that host. Every REST route lives under /api/v1. The routes that are not under it are webhook URLs (/webhooks/…) and the WebSocket (/ws). The URL Natiq gives you for a webhook is complete; use it as it is.
Headers
| Header | When | Value |
|---|---|---|
Authorization | Every call except /auth/login, /auth/register and password and invitation routes | Bearer <accessToken>. See Authentication. |
Content-Type | Requests with a body | application/json |
Accept | Optional | application/json |
Request-Id | External bot publish and outbound calls | A unique id per message, so a retry sends nothing twice. See External agents. |
Ids in paths and bodies are numbers unless an endpoint says otherwise (flow run ids are UUID strings).
Query parameters
Send arrays as repeated keys (what the dashboard sends); bracket syntax isn't understood:
GET /api/v1/contacts?q=layla&page=0&size=50
GET /api/v1/<resource>?tag=vip&tag=gold # an array: one key per valueNatiq's own dashboard drops empty values (q=) before sending. Do the same: an empty string is not "no filter" on every endpoint.
Errors
A failed call answers a 4xx or 5xx status and one JSON envelope. Errors raised by Natiq use this envelope. Framework-level failures — an unknown route, a wrong method, a malformed query parameter, an expired token, an unexpected 500 — may not, so handle them by status.
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "must be a well-formed email address", "code": "INVALID_FIELD" }
]
}
}| Field | Notes |
|---|---|
error.code | A stable code. Branch on this, never on message. |
error.message | Words for a person, in English. It can change. |
error.details | Always present; empty unless validation failed. For validation errors, one entry per bad field: field, message and code (INVALID_FIELD). |
error.data | Optional. For some refusals, the current state behind them (for example who won a claim). |
| Status | Meaning |
|---|---|
400 | Malformed request or validation failure (VALIDATION_FAILED, INVALID_REQUEST). |
401 | No valid token (UNAUTHENTICATED) or wrong credentials at sign-in. Sign in again. |
403 | The user lacks the permission (PERMISSION_DENIED, or another specific code such as ORG_SUSPENDED). |
404 | Not found in your organisation, with a specific code such as CONTACT_NOT_FOUND or CHANNEL_LINE_NOT_FOUND. |
409 | A conflict with current state. |
412 | A precondition failed, for example a stale version. |
422, 429 | Unprocessable, rate limited (429 carries Retry-After). |
502 | An upstream service failed. |
503 | Unavailable. It may carry Retry-After. |
An oversized file upload answers 400 VALIDATION_FAILED, not 413.
A response with no JSON body, such as a gateway page, has no error object. Handle that case by status alone. See also Errors.
Pagination
List endpoints use one of two styles. Each API reference page says which.
Offset pages
GET /api/v1/contacts?page=0&size=20| Parameter | Notes |
|---|---|
page | Zero-based. |
size | Page size (default 20). Keep it at 100 or below; some endpoints clamp it to that range. |
{ "data": [ … ], "page": 0, "size": 20, "totalElements": 134, "totalPages": 7 }async function* allContacts(get) {
for (let page = 0, pages = 1; page < pages; page++) {
const body = await get(`/contacts?page=${page}&size=100`)
pages = body.totalPages
yield* body.data
}
}def all_contacts(get):
page, pages = 0, 1
while page < pages:
body = get("/contacts", params={"page": page, "size": 100})
pages = body["totalPages"]
yield from body["data"]
page += 1Keyset pages
Timelines return a plain array, newest first, and use a cursor:
GET /api/v1/contacts/1842/timeline?size=50&afterId=0afterId=0 (or omitted) is the newest page. For the next, older page, pass the smallest id from the page you just read. An empty array means there is nothing older. A full page usually means more exists; an empty one confirms it does not.
Timeouts and rate limits
Natiq does not publish a standard request timeout for the REST API; the dashboard itself waits 30 seconds, and a good default for your client is the same. Some calls are intentionally long (a flow dry run can take minutes), so set a longer limit per call where you know it.
Some endpoints are rate limited. They answer 429 with a Retry-After header in seconds; wait that long and retry. Check the endpoint's own page for its limits rather than assuming a global one.