Skip to main content
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:
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 and for 30 days after it, the old cell answers 421 wrong_cell to any key or person of the space, instead of serving what it still holds, and the SDKs retry at once on a new connection, which the edge routes to the new cell. The internal topology is never part of the contract: a space on a dedicated cell of the Regulado plan has the same address shape. The control API, for tenants, projects, sources, keys, people, configuration and usage, lives at https://control.api.niadra.com. The generic webhook also answers at https://mtls.<region>.api.niadra.com, the address that asks for a client certificate in the handshake, for sources authenticated by mtls; see System webhooks.

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, on every response of the old version, Deprecation: @<instant> (RFC 9745), which says since when, and Sunset: <HTTP date> (RFC 8594), which says when it stops, exposed through CORS with the Link of rel="deprecation". Both SDKs log a warning, once per route, when a response carries Deprecation. Today nothing in /v1 has been deprecated, so no response carries those headers. The API versioning policy says what counts as a breaking change and how the notice arrives.

Authentication

The login that confirms the authenticator also returns ten recovery codes, once. When the app is lost, each code works once, in recovery_code in place of totp, and the answer says how many remain in recovery_codes_left. Niadra keeps only an HMAC-SHA256 of each code. A person generates new codes and changes the app with the password again, and an admin resets the second factor of someone who lost both the app and the codes. New people arrive by invitation, a single-use link to choose a password, or through the company’s SSO, over OIDC or SAML 2.0. A deactivated person loses the control plane at once; a token already issued keeps working on the data API until it expires. 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, analytics, agent_memory, agent_memory:write, replay, coordinate, state:push and agent_state; the last four apply only in a space that turned their feature on. See 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 and cutting a source in the cell. 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. The writes of the agent features require it too: declaring, claiming, reserving an effect, creating and accepting a handoff (coordination); promoting turns, creating a scenario and reporting a run (turn records); creating a legal hold, a review request and releasing content. Turn recording and state pushes use, instead of the header, each record’s turn_id and each item’s version: the same turn sent twice is one turn, and a field moves only under a greater version.

Errors

Route 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.

Pagination

Lists paginate by an opaque cursor, in a stable order. 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. A space created a few seconds ago, or one being restored, answers 503 unavailable with Retry-After: 5 while its database is prepared, for up to about a minute. The SDKs send the events of their queue again on their own, keeping them queued until it works; a context or history read is not repeated and comes back empty. A client of the raw API should honour Retry-After and repeat the request.

Caching and ETag

The context has an ETag. The same request with the same state gives the same bytes. 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 takes its query in the body. The GET forms of context and timeline take a profile id instead. Ids that are not personal data, such as a profile id, an item id or an object id, may appear in paths. A conversation_id may be the customer’s phone number, e-mail address or WhatsApp id, so every read that takes one has a POST form, with the id in the body: The Console and the SDKs use the POST forms of the reads they make. The GET forms stay in /v1; if you use one of them, send an opaque conversation id from your system.

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

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.

Memory never cuts

The sizes above are a request’s: a larger request is refused with an error, never half taken. What goes in, memory stores and learns whole, and never deletes on its own:
  • A long turn, e-mail or document is read in parts, in order and with overlap, never cut at its start, its end or at N characters. A file is read whole, in windows of 50 pages, and a text over 100 thousand characters becomes several turns, each with its part (events).
  • Summaries, resolutions, values, open items, instructions, agent notes and links are stored whole, with no cap on how many.
  • A batch that fails every attempt is kept and can be tried again.
  • What leaves memory leaves through an erasure the data subject asks for and through the retention the space configures, and both reach everything, kept batches included.
Only a read has a size: the block that goes to the agent. When it leaves something out, it says how much and where the rest is: left_out in the notes block, omitted in the constraints block, open_objects_more in a handoff package, partial on a derived field, “mais N objetos deste cliente fora desta lista” and “mais N registros (consulte o histórico)” on the context’s lines (in the space’s language), “(continua no histórico)” on a long quoted text, and a cursor on the lists that come in pages. The rest is in the history’s search and open.

Speed

No LLM runs in the path of context() or of the navigation. The times measured in the region are on the benchmark.

Starting caps of a space

These are the caps of every space today. 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

Errors

every code, and what to do with it.

Events and the batch

idempotency and per-item errors in practice.

Spaces and keys

scopes, sources and the key format.

The Niadra API

every route, generated from the OpenAPI.