Skip to main content
The niadra package is the Python SDK: a synchronous client, Niadra, and an asynchronous twin, AsyncNiadra, with the same methods. It is open source under Apache 2.0, needs Python 3.10 or later, and depends only on httpx and pydantic. Every method on this page maps to a route of the API reference.

Install

The client

AsyncNiadra takes the same arguments, with http_client: httpx.AsyncClient. One instance per process is enough, and it is thread-safe. The client also works as a context manager: leaving the block calls close(), and an exit hook flushes the queue for up to two seconds.

Properties

Safe by default, strict on request

Without a key the client warns once and does nothing. Every public method catches and logs its own failures and returns a safe value: an empty Context (check context.error), an empty result, False or None. Logs carry method names, status codes, error codes and request ids, never handles or text. With strict=True the same failures raise the exceptions listed under Errors.

Time budgets

The SDK keeps its own time budget per method, whatever the platform around it allows.
Retries: 5xx answers, 429 and network errors are retried with backoff; 421 (the space is moving between cells) is retried at once on a fresh connection; other 4xx answers are final.

The context cache

Inside a conversation or task (a call with conversation_id or task_id), packs are cached in memory:
  • younger than 10 s: returned without a request;
  • up to 10 minutes older: returned at once while one background request refreshes it;
  • when a request fails: the last good pack, if it is less than 30 minutes old;
  • at most 1,000 packs, the least recently used evicted first.
Refreshes send the cached ETag, so an unchanged pack costs a not_modified answer instead of the full text, and only one refresh per pack runs at a time. A 401 or 403 is not an outage: the cached packs go (all of them on 401, the one requested on 403), so cutting a vendor’s access also cuts what it had cached. A plain read and a delta read of one conversation share one entry, and each delta is handed out once.

The write queue

track(), action() and handoff() only queue. A background thread (a task, with AsyncNiadra) sends a batch when 15 items are waiting or one second after the first arrived, with three attempts and backoff. When the queue is full, new items are dropped and counted in dropped.

Handles

Handles identify a subject in a channel or system. The helpers are top-level functions that normalize the value and raise ValueError on input that cannot be valid, so a malformed phone fails where it enters your code. Every method that takes a handle also accepts a Handle model or a mapping with its fields ({"type": "phone_e164", "value": "+14155550123"}). Objects are accepted as "invoice:erp:0823" or as an ObjectRef.

context()

The context pack for a person, an organization or a business object. It maps to POST /v1/context.
Returns a Context: every field of the API response plus what the SDK knows about the call.
Searches the whole history of one subject by keyword and meaning. Maps to POST /v1/history/search.
Returns a SearchResult with items, recurrence (how many times the same category happened and how the last one ended), withheld, as_of, tokens_used, degraded and, on failure, error.

timeline()

One page of the subject’s history, most recent first. Maps to POST /v1/history/timeline.
limit goes from 1 to 100. Returns a TimelinePage with items, next_cursor, withheld, as_of and, on failure, error. Pass next_cursor as cursor to go on.

open()

Opens one episode or object found by search() or timeline(). Maps to GET /v1/history/items/{item_id}.
Returns an OpenedItem (summary, requested, promises, outcome, resolution, derived, timeline) or None when it is unavailable. The literal excerpt only comes back to keys with an elevated scope.

object_state() and object_timeline()

Reads of a business object. They map to GET /v1/objects/{object_type}/{namespace}/{external_id} and its timeline.
ObjectState carries ref, state, as_of, source_id, record_ref and the open_items. The state comes only from what the systems of record reported; an agent’s action counts once a system confirms it. ObjectTimeline carries ref, items, next_cursor and as_of: system events and agent actions, newest first, never what anyone said. Object ids are record ids, not personal data, so they go in the URL; an id that contains a slash cannot be addressed this way.

track()

Queues a message, a system event or an action, or any other batch item, and returns at once. Maps to POST /v1/batch.
Accepts an EventItem or a mapping of its fields. A mapping without channel gets the client’s default channel; idempotency_key (a UUIDv7) and occurred_at (now) are filled in when you leave them out. Use the provider’s message id as idempotency_key when there is one, so a retried batch never duplicates anything. Returns False when the item was dropped: invalid, not serializable, queue full or client disabled.

action()

Records what an agent did in a system of record. Queued, like track().
The action stays declared until the system of record confirms it with its own event. Recording actions needs the act scope on the key.

identify()

States that two or more handles belong to the same subject. Sent at once rather than queued, so a context() that follows sees it; if the request fails, the item is queued for the background sender and None is returned.

verify()

Raises the verification level of one conversation or task, after you proved it. Niadra never infers it. Sent at once, and the cached pack of that conversation is dropped, so the next context() reflects the new level.
method is otp_whatsapp, otp_sms, login, kba, network_attestation or human_agent. A level above the ceiling of the source comes back as a verification_not_allowed item error.

feedback()

Corrects what Niadra derived about a subject. Sent at once and recorded as an event, so it is audited like any other. Maps to POST /v1/feedback.
Each action takes its own fields: value goes up to 2,000 characters and reason up to 500. Returns None when the correction could not be delivered.

upload_media()

Hands a file to Niadra, such as a call recording, and returns the reference for its event. Media never travels inside an event. Maps to POST /v1/media/uploads.
The method hashes the bytes, reserves an upload, and sends the bytes straight to storage over the short-lived signed URL. It sends exactly the upload_headers the API returned, which are the headers the signature covers, and nothing else: never your key. The URL must be HTTPS, except against the local emulator. Storage checks the body against the declared size and digest. With subject, the file is stored under that person, so erasing them erases it even if no event ever references it. Returns a MediaUpload with media_ref, media_sha256, content_type, size_bytes and expires_at, or None when the upload failed. Each attempt of the transfer has the upload budget, 60 s by default.

handoff()

Records a transfer to a human ("human") or another agent ("agent"). Queued. The context-use measurement reads it to count handoffs where the receiver never read the context.

conversation()

A conversation with one customer, used as a context manager. Leaving the block emits conversation.ended, even when the block raised.
Without a conversation_id the SDK mints one. channel defaults to the client’s. agent_id identifies your agent within the source and is stamped on its turns and actions. The first context() gets the pack the server pins for the conversation. Later reads also ask for the delta, and the conversation keeps every delta it receives, in order, so turn_block carries all the changes since the pin, followed by the live turns. When the server pins a new pack, after verify() for instance, the kept deltas are dropped: the new pack already has them. A read with query= is a one-off and leaves them alone. Call mark_injected() each time you put the pack in a prompt. It is how Niadra tells a context that arrived after the agent spoke from one the agent had and did not use. The session also keeps context_injected_at and first_agent_turn_at, the first of each, for your own checks.
current_session() returns the conversation or task whose block is running in the current thread or task, if any.

task()

The same for an internal agent (billing, orders, tickets). It emits task.ended on exit.
With an object, the pack is centered on it; use a task view such as task:billing. A task has the same methods as a conversation except handoff().

tools()

The history kit as function-calling tools, bound to one customer. The customer is bound here, outside the model’s reach: the model picks the query, never the profile, which is what stops a prompt injection from switching customers.
AsyncNiadra.tools() returns an AsyncToolKit, whose call() is awaited. The definitions are also served by GET /v1/history/tools.

subject_token()

Mints a signed 15-minute token that binds an MCP connection to one customer. Call it from your backend. Maps to POST /v1/subject-tokens.
Returns a SubjectToken with token, expires_at and headers, the Niadra-Subject-Token header to send with the source key when the agent opens the connection to mcp_url. With about, the organization is bound like the subject. See MCP with any LLM.

wrap()

Wraps the OpenAI Python client, or any client with the same shape, so every call inside a conversation or task block gets the context and records the answer.
Inside a block (or for the session you pass), chat.completions.create and chat.completions.parse, sync or async, streaming or not, get the pinned pack as a system message right after your own leading system messages, and the turn block as a system message at the end. The injection is stamped with mark_injected(), and the model’s answer is recorded as the agent’s turn: when a stream ends or is closed and, through with_raw_response, when you call parse(). Outside a block, calls pass through untouched. The wrapper returns a proxy and never modifies your client. Nothing it does can fail your model call: a context it cannot fetch is left out, and a failure to record the answer is logged, without content.

flush() and close()

flush() sends everything queued from the calling thread and returns True when nothing is left. close() flushes for up to timeout seconds and releases connections. With AsyncNiadra, both are awaited.

AsyncNiadra

AsyncNiadra has the same methods. The reads, identify(), verify(), feedback(), upload_media(), subject_token(), flush() and close() are awaited; track(), action(), handoff(), conversation(), task() and tools() are not. Conversations and tasks are async context managers.

Errors

With strict=True, or from helpers you call directly such as ApiKey.parse() and the handle functions, the SDK raises: The full code catalog is in Errors. Quote request_id when you contact support.

Local emulator

niadra-mock, installed with the package, is a local in-memory emulator of the same API. In tests, run the SDK against it in-process:
Recent turns become a small pack, deltas are sent once per change, search matches keywords, verification only rises through verify(), objects take their state from system events, feedback becomes a feedback.* event and media uploads land in mock.cell.media. mock.cell also lets you inspect events and inject failures (fail_next, revoke, cut, put_in_holdout). From a shell, niadra-mock --port 8765 serves it over HTTP.

Next steps

TypeScript SDK

The same surface for Node and edge runtimes.

Quickstart

From a key to the first delivered context.

Context and views

What goes into the pack and why.

Errors

The code catalog and what to do with each code.