# Overview

> Conventions of the REST API: address, versions, authentication, error format, pagination, identifiers and dates.

The full reference is **generated from the API's real OpenAPI specification**: it cannot drift from it. Each operation comes with a sample call (curl, Node.js, Python, PHP) and the **real** response recorded from the API.

<Callout type="tip" title="Try it live">
The API's interactive console (`/api/docs`) lets you call each route from your browser: click "Authorize" and paste a **sandbox** key. The raw specification is at [`/openapi.json`](/openapi.json).
</Callout>

## Address and version

```text
https://YOUR-HOST/api/v1
```

The major version is in the address (`/v1`). Changes that don't break integrations (new fields, new routes, new events) are added to `/v1`: **ignore unknown fields** in your responses. A breaking change would ship as a new version. See the [changelog](/docs/changelog).

## Authentication

`Authorization: Bearer <token>`. There are two kinds of tokens: the console **session** (`ses_…`) and the **API key** (`pk_test_…`, `pk_live_…`). See [Authentication and keys](/docs/authentication). Each operation states which one it requires and the scope needed. Key-based routes also accept the console session, with an `X-Environment-Id` header.

## Request and response format

- **JSON** bodies (`Content-Type: application/json`), in **UTF-8**.
- **Identifiers**: UUIDs, except Meta identifiers (`wabaId`, `phoneNumberId` when connecting), which are strings of digits.
- **Dates**: ISO 8601, in **UTC** (`2026-11-12T06:09:17.388Z`).
- **Unknown fields** in a request are rejected (`400 validation_error`), so a typo is caught quickly.
- Resources belonging to **another account or another environment** answer `404`, as if they didn't exist.

## Errors

All errors have **the same format**, whatever their origin:

```json
{
  "error": {
    "code": "validation_error",
    "message": "Invalid request: see details",
    "details": ["email must be an email"],
    "requestId": "req_0a1b2c3d-…"
  }
}
```

| Field | Description |
|---|---|
| `code` | Stable identifier, to use in your code. |
| `message` | Human-readable explanation, in French by default (English with `Accept-Language: en`). Do not parse its text. |
| `details` | (validation) The list of fields at fault. |
| `requestId` | Give it to support. Identical to the response's `X-Request-Id` header. |

Full list: [Error codes](/docs/errors).

## Pagination

Long lists are paginated **by cursor**, from newest to oldest.

```json
{ "data": [ … ], "nextCursor": "MjAyNi0xMS0xMlQw…" }
```

As long as `nextCursor` is not `null`, pass it in `?cursor=` to get the next page. `?limit=` sets the page size (1 to 100; 1 to 200 for logs). Never build a cursor yourself.

## Request ID

Every response carries an `X-Request-Id` header. You can provide your own (8 to 64 characters: letters, digits, `.`, `_`, `-`) to correlate your logs with ours. It appears in the [request log](/docs/api/logs).

## Idempotency

`POST /messages` accepts an `Idempotency-Key` header. See [Reliability](/docs/guides/reliability).

## Calls from a browser

The API is meant to be called **from your server**. Calls from a browser (CORS) are not allowed: an API key placed in a web page would be public.
