> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lekalao.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Conventions

> Format, identifiers, pagination, rate limits, idempotency and CORS.

## Address and format

* Every route lives under `https://lekalao.example.com/api/v1`.
* Send and receive JSON: `Content-Type: application/json` and `Accept: application/json`.
* A single resource is wrapped in `data`: `{"data": {…}}`.
* Dates are ISO 8601 with a time zone: `2026-09-17T09:12:44+00:00`. Without one, a date you send is read in the installation's time zone (UTC by default).
* Amounts are integers in the smallest unit of the currency: `1250` is 12.50 €. For the CFA franc, which has no subdivision, `15000` is 15,000 FCFA.

## Identifiers

Every resource is named by its `id`, which is a uuid and never a row number. Addresses are built with it: `/api/v1/lists/{id}/subscribers`.

Tags may also be named by their **name** where that reads better: a subscriber's `tags`, segment rules.

## Pagination

Listings are paginated:

| Parameter  | Default          | Maximum |
| ---------- | ---------------- | ------- |
| `page`     | 1                |         |
| `per_page` | 25 (50 for tags) | 100     |

```json theme={null}
{
  "data": [ … ],
  "links": {
    "first": "https://lekalao.example.com/api/v1/lists/9d5c…/subscribers?page=1",
    "last": "https://lekalao.example.com/api/v1/lists/9d5c…/subscribers?page=52",
    "prev": null,
    "next": "https://lekalao.example.com/api/v1/lists/9d5c…/subscribers?page=2"
  },
  "meta": { "current_page": 1, "from": 1, "last_page": 52, "per_page": 25, "to": 25, "total": 1284 }
}
```

Follow `links.next` until it is `null`.

## Rate limit

Each **token** may make 120 calls a minute (the installation can change it). Every answer says where you stand:

```http theme={null}
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1789637580
```

Past that, the answer is `429` with `Retry-After` in seconds:

```json theme={null}
{ "message": "Too many requests. Try again in 23 seconds." }
```

Wait `Retry-After` seconds before starting again. To subscribe many people, use the [batch](/developers/subscribers#subscribe-in-a-batch): one call for 1,000 addresses.

## Idempotency

A call that times out leaves you guessing: did the order go through? Add an `Idempotency-Key` header to your `POST` and `PUT` calls and send the same call again without risk:

```bash theme={null}
curl -X POST https://lekalao.example.com/api/v1/transactional-mails/send \
  -H "Authorization: Bearer $LEKALAO_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-2026-00481-confirmation" \
  -d '{"template": "order-confirmation", "to": ["ada@example.com"], "variables": {"order": "2026-00481"}}'
```

| Situation                                        | Answer                                                                                 |
| ------------------------------------------------ | -------------------------------------------------------------------------------------- |
| First time                                       | Handled normally. The answer carries `Idempotency-Key`.                                |
| Same key, same route, same body, within 24 hours | The first answer, replayed exactly, with `Idempotent-Replay: true`. Nothing is redone. |
| Same key while the first call is still running   | `409`. Try again a little later.                                                       |
| Same key, another route or another body          | `422`: a key belongs to one request.                                                   |
| Same key after 24 hours                          | `422`: the key has expired, send a new one.                                            |
| First call failed with a `5xx`                   | Nothing is kept: sending it again is treated as new.                                   |

Choose a key that describes the operation (order number plus action) or a UUID generated before the first attempt. 255 characters at most. Keys belong to one team.

## CORS

The `api/*` and `subscribe/*` routes accept calls from any origin, without cookies. The rate-limit and idempotency headers are readable by the browser.

<Warning>
  Open CORS does not mean you should call the API from a browser: the token
  would be visible to everyone. Only the [subscription
  form](/developers/forms) is made for that.
</Warning>
