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

# Limits and conventions

> Versioning, authentication, idempotency, pagination, rate limits, ETag and personal data kept out of URLs.

The same rules apply to every route of the Niadra API: the data API of your space and the control API. Knowing them once saves you from reading them again on each endpoint.

## Address

Every space has a stable address, with its region in the name:

```text theme={null}
https://<space>.<region>.api.niadra.com
```

The key carries the region and the space, never the cell that serves them, and the SDKs derive the address from the key. When a space moves between cells, the address stays the same; during the move, the old cell answers 421 and the SDKs retry at once on a new connection. The internal topology is never part of the contract.

The control API, for tenants, projects, sources, keys, people, configuration and usage, lives at `https://control.api.niadra.com`.

## Versions

The version is in the path: `/v1`. A new field or a new route does not change the version, so your code must ignore fields it does not know (both SDKs do). Removing a field or changing its meaning is `/v2`, with 12 months in which both versions answer, a notice by e-mail and a `Deprecation` header on every response of the old version.

## Authentication

| Who                                                 | How                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Agents and systems                                  | `Authorization: Bearer nia_sk_<live\|test>_<region>_<space>_<key_id>_<secret>`, a source key with scopes per route.                                                                                                                                                                                                                                                                                                                          |
| People (Console, governance routes and control API) | `Authorization: Bearer <token>`, issued by [`POST /v1/auth/login`](/en/api/control/login) (e-mail, password and a TOTP code; on the first sign-in, the authenticator is set up through [`POST /v1/auth/second-factor`](/en/api/control/second-factor)) and renewed by [`POST /v1/auth/token`](/en/api/control/token). A token lives 15 minutes and names one space. Cells verify it offline against the [public keys](/en/api/control/jwks). |

A source key only authenticates with its secret; the `key_id` in its prefix is public. The scopes are `track`, `act`, `context`, `search`, `identify`, `admin` and `analytics`. See [Spaces and keys](/en/concepts/spaces-and-keys).

Governance routes (identity, privacy, audit, measurement, review, triggers) accept either a person with one of the route roles or a source key with the `admin` scope. The Console roles are `admin`, `security`, `integration`, `review`, `analysis` and `vendor`; `admin` passes every role check, and a `vendor` person only sees the sources named in their role binding. Review verdicts are the exception: only a person with the `review` role records them.

## Idempotency

Routes that change identity or erase data take an `Idempotency-Key` header, and on them it is **required**: every identity write (create or retract an assertion, merge, unmerge, block a handle, create or end a link, accept or dismiss a suggestion), [`POST /v1/forget`](/en/api/forget) and [cutting a source in the cell](/en/api/source-revoke). Niadra keeps the key for 24 hours with a hash of the body:

* the same key with the same body returns the first answer again;
* the same key with a different body returns 409 `conflict`.

The batch uses the `idempotency_key` of each item instead: the provider message id, or a UUIDv7 minted by the SDK. A repeated key is counted in `duplicates` and never stored twice, even when a webhook is resent days later.

## Errors

Errors answer `application/problem+json` (RFC 9457) with `type`, `title`, `status`, `detail`, `code` and `request_id`. The batch answers 200 when every item was accepted, and 207 with one error per rejected item otherwise. The full catalog is in [Errors](/en/errors).

## Pagination

Lists paginate by an opaque cursor, in a stable order.

| Parameter | Description                                                                                                  |
| --------- | ------------------------------------------------------------------------------------------------------------ |
| `limit`   | Items per page: up to 200 on lists, up to 100 on timelines, up to 50 on profile search and the review queue. |
| `cursor`  | The `next_cursor` of the previous page.                                                                      |

`next_cursor` is `null` on the last page. Never build a cursor yourself: its content may change without notice.

## Rate limits

Over the limit, the answer is 429 `rate_limited` with `Retry-After` in seconds. Limits apply per key, and writes (`/v1/batch`, `/v1/ingest`, `/v1/otel`, `/v1/media`, `/v1/feedback`) are counted apart from reads, so a flood of writes never cuts reads first. A 503 `unavailable` also carries `Retry-After`.

## Caching and ETag

Every deterministic read has an ETag: the context, the history navigation and business objects. The same request with the same state gives the same bytes.

| Route                            | How to ask "has it changed?"                                                                             |
| -------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `POST /v1/context`               | `known_etag` in the body. When nothing changed, the answer is 200 with `not_modified: true` and no text. |
| `GET /v1/context?profile_id=...` | `If-None-Match` header. When nothing changed, the answer is 304.                                         |

The SDKs send the ETag they hold on every context refresh.

## Personal data stays out of URLs

Phone numbers, e-mail addresses, documents, handles, search queries and any free text travel only in request bodies, never in paths or query strings. URLs end up in load balancer logs, firewall logs and tracing spans, outside the encryption and outside the reach of `forget`. That is why `context`, `search` and `timeline` by handle are `POST`, and why [profile search](/en/api/profiles-search) takes its query in the body. The `GET` forms of context and timeline take a profile id instead. A test in our CI with a canary value makes sure none of this appears in a URL, a log, a span or an error message.

Ids that are not personal data, such as a profile id, an item id, a `conversation_id` or an object id, may appear in paths.

## Tracing

Send a W3C `traceparent` header and Niadra returns it on the response. Send `X-Request-Id` to choose the request id, or let Niadra mint one; every response carries it in the `X-Request-Id` header and in the `request_id` of an error. Quote it when you contact support.

## Sizes

| What                               | Limit                                                                                                                                                                          |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Items per batch                    | 500                                                                                                                                                                            |
| Batch body                         | 2.5 MB                                                                                                                                                                         |
| One event                          | 1 MB                                                                                                                                                                           |
| Text or transcript of an event     | 200,000 characters                                                                                                                                                             |
| Handles per event                  | 16                                                                                                                                                                             |
| Subjects per event                 | 8                                                                                                                                                                              |
| Objects per event                  | 16                                                                                                                                                                             |
| Conversation aliases               | 8                                                                                                                                                                              |
| Handles in one `identify`          | 2 to 16                                                                                                                                                                        |
| Media file                         | 500 MB, uploaded to a presigned URL from [`POST /v1/media/uploads`](/en/api/media-uploads), with exactly the `upload_headers` it returns. Media never travels inside an event. |
| File import                        | 512 MB, `text/csv` or `application/x-ndjson`, each record up to 1 MB and a CSV header up to 64 KB. See [`POST /v1/ingest/files`](/en/api/ingest-files).                        |
| Search `query` and context `query` | 2,000 characters                                                                                                                                                               |
| Search answer (`max_tokens`)       | 50 to 4,000 tokens; 800 by default, 300 recommended for voice                                                                                                                  |

A batch body over 2.5 MB, or a batch with more than 500 items, answers 422 `invalid_input`. An item over 1 MB is rejected on its own, with the item error `too_large`, and the rest of the batch goes in.

## Speed

| Operation                                         | Target, in the region |
| ------------------------------------------------- | --------------------- |
| `context()`                                       | under 100 ms          |
| History navigation (`search`, `timeline`, `open`) | under 200 ms          |
| Ingestion acknowledgement                         | under 80 ms           |
| A new message, action or system event in `live`   | under 1 second        |
| An action or system event in the pack             | under 10 seconds      |
| Memory derived from a finished conversation       | under 60 seconds      |

No LLM runs in the path of `context()` or of the navigation.

## Starting caps of a space

| Resource                                | Starting cap                                                             |
| --------------------------------------- | ------------------------------------------------------------------------ |
| Task views                              | 8 per space                                                              |
| Pack variants per profile               | 32; the least read one goes first                                        |
| Active links per person                 | 10                                                                       |
| Detailed contacts in the `account` view | 20, plus a count of the others                                           |
| Active patterns per profile             | 20                                                                       |
| Custom pattern rules                    | 25; window up to 365 days and never beyond the retention of the evidence |
| Trigger rules                           | 50 per space; 100 firings per minute                                     |
| Webhook dead-letter queue               | 7 days                                                                   |
| Continuous export                       | 1 schedule per destination; at least 1 hour apart                        |
| Sampled review                          | 500 samples per day; kept 12 months                                      |
| Configuration assistant                 | 20 runs per month; 200 samples per run                                   |
| Trigger firings and export runs         | kept 13 months                                                           |
| Events of an unmapped type              | 7 days in cold storage, for remapping                                    |

These are starting values, set in your contract. Ask us when a use case needs more.

## Names in the contract

Every name in the API, the SDKs, the MCP tools, the events and the webhooks is English ASCII. Fields are `snake_case` (`conversation_id`, `occurred_at`); times are ISO 8601 with a time zone; enums use fixed lowercase values, such as `message`, `system_event` and `action` for `kind`, or `customer`, `ai_agent`, `human_agent` and `system` for `speaker`. Verification levels are `V0` to `V4` and `no_customer`. Within `/v1` no value ever changes meaning.

## Next steps

<CardGroup cols={2}>
  <Card title="Errors" href="/en/errors">
    every code, and what to do with it.
  </Card>

  <Card title="Events and the batch" href="/en/concepts/events">
    idempotency and per-item errors in practice.
  </Card>

  <Card title="Spaces and keys" href="/en/concepts/spaces-and-keys">
    scopes, sources and the key format.
  </Card>

  <Card title="The Niadra API" href="/en/api">
    every route, generated from the OpenAPI.
  </Card>
</CardGroup>
