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

# SDK de Python

> Cada método da biblioteca niadra, com parâmetros, retorno e exemplo.

A biblioteca `niadra` é o SDK de Python: um cliente síncrono, `Niadra`, e um gêmeo assíncrono, `AsyncNiadra`, com os mesmos métodos. É de código aberto, sob Apache 2.0, pede Python 3.10 ou mais novo e depende só de `httpx` e `pydantic`. Cada método desta página corresponde a uma rota da [referência da API](/api).

## Instalação

```sh theme={null}
pip install niadra
```

## O cliente

```python theme={null}
from niadra import Niadra

niadra = Niadra()  # reads NIADRA_API_KEY
```

```python theme={null}
Niadra(
    api_key: str | None = None,
    *,
    base_url: str | None = None,
    channel: str | None = None,
    strict: bool = False,
    timeouts: Timeouts | None = None,
    cache: CacheOptions | None = None,
    queue: QueueOptions | None = None,
    http_client: httpx.Client | None = None,
)
```

| Parâmetro     | Tipo           | Padrão                                      | Descrição                                                                                                                         |
| ------------- | -------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `api_key`     | `str`          | `NIADRA_API_KEY`                            | Uma chave de fonte, `nia_sk_<live\|test>_<region>_<space>_<key_id>_<secret>`.                                                     |
| `base_url`    | `str`          | `NIADRA_BASE_URL`, depois derivado da chave | A chave diz a região e o espaço, então o endereço é `https://<space>.<region>.api.niadra.com`. Troque para usar o emulador local. |
| `channel`     | `str`          | `None`                                      | O `channel` padrão dos eventos que não dizem o seu, como `"whatsapp"`.                                                            |
| `strict`      | `bool`         | `False`                                     | Levanta exceção em vez de registrar em log e devolver um valor seguro. Use nos testes.                                            |
| `timeouts`    | `Timeouts`     | veja abaixo                                 | O tempo máximo de cada método.                                                                                                    |
| `cache`       | `CacheOptions` | veja abaixo                                 | O cache de contexto por conversa.                                                                                                 |
| `queue`       | `QueueOptions` | veja abaixo                                 | O envio em lote de `track()`.                                                                                                     |
| `http_client` | `httpx.Client` | `None`                                      | Um cliente seu, para proxy ou transporte próprio.                                                                                 |

`AsyncNiadra` recebe os mesmos argumentos, com `http_client: httpx.AsyncClient`. Uma instância por processo basta, e ela é segura entre threads. O cliente também funciona como gerenciador de contexto: sair do bloco chama `close()`, e um gancho de saída esvazia a fila por até dois segundos.

### Propriedades

| Propriedade | Tipo   | Descrição                                                                                 |
| ----------- | ------ | ----------------------------------------------------------------------------------------- |
| `enabled`   | `bool` | `False` quando não há chave utilizável; toda chamada vira então uma operação vazia.       |
| `base_url`  | `str`  | O endereço com que o cliente fala.                                                        |
| `mcp_url`   | `str`  | O endpoint MCP do espaço, `<base_url>/mcp`.                                               |
| `pending`   | `int`  | Itens esperando na fila local.                                                            |
| `dropped`   | `int`  | Itens descartados até agora: fila cheia, recusados pela API ou impossíveis de serializar. |

### Seguro por padrão, estrito quando você pede

Sem chave, o cliente avisa uma vez e não faz nada. Cada método público captura e registra as próprias falhas e devolve um valor seguro: um `Context` vazio (veja `context.error`), um resultado vazio, `False` ou `None`. Os logs levam nome do método, status, código de erro e id da requisição, nunca handle nem texto. Com `strict=True`, as mesmas falhas levantam as exceções de [Erros](#errors).

### Tempo máximo

O SDK tem o próprio tempo máximo por método, independente do que a plataforma em volta permite.

```python theme={null}
from niadra import Timeouts

Timeouts(
    context=0.30,           # context(), every view except voice
    context_voice=0.15,     # context() with view="voice"
    navigation=0.60,        # search(), timeline(), open(), object reads
    navigation_voice=0.30,  # the same, with voice=True
    write=5.0,              # each attempt of a write sent at once
    upload=60.0,            # each attempt of sending media bytes to storage
)
```

Novas tentativas: respostas 5xx, 429 e erros de rede são repetidos com espera crescente; 421 (o espaço está mudando de célula) é repetido na hora, numa conexão nova; as outras respostas 4xx são definitivas.

### O cache de contexto

Dentro de uma conversa ou tarefa (uma chamada com `conversation_id` ou `task_id`), os contextos ficam em memória:

* com menos de 10 s: devolvidos sem requisição;
* até 10 minutos mais velhos: devolvidos na hora, enquanto uma requisição em segundo plano atualiza;
* quando uma requisição falha: o último contexto bom, se tiver menos de 30 minutos;
* no máximo 1.000 contextos, saindo primeiro o usado há mais tempo.

A atualização manda o ETag guardado, então um contexto que não mudou custa uma resposta `not_modified` em vez do texto inteiro, e só uma atualização por contexto roda de cada vez. Um 401 ou 403 não é indisponibilidade: os contextos guardados saem (todos no 401, o pedido no 403), então cortar o acesso de um fornecedor corta também o que ele tinha em cache. Uma leitura comum e uma leitura com `delta` da mesma conversa dividem a mesma entrada, e cada delta é entregue uma vez só.

```python theme={null}
from niadra import CacheOptions

CacheOptions(enabled=True, ttl=10.0, stale_while_revalidate=600.0, max_stale=1800.0, max_entries=1000)
```

### A fila de escrita

`track()`, `action()` e `handoff()` só enfileiram. Uma thread em segundo plano (uma task, no `AsyncNiadra`) manda um lote quando 15 itens estão esperando ou um segundo depois de o primeiro chegar, com três tentativas e espera crescente. Com a fila cheia, os itens novos são descartados e contados em `dropped`.

```python theme={null}
from niadra import QueueOptions

QueueOptions(capacity=10_000, batch_size=15, interval=1.0, heartbeat_interval=60.0)
```

## Handles

Handles identificam um sujeito num canal ou sistema. Os ajudantes são funções de primeiro nível que normalizam o valor e levantam `ValueError` quando a entrada não pode ser válida, então um telefone malformado falha no ponto em que entra no seu código.

| Função                                      | Handle                                                         | Exemplo                                  |
| ------------------------------------------- | -------------------------------------------------------------- | ---------------------------------------- |
| `phone(number)`                             | `phone_e164`                                                   | `phone("+14155550123")`                  |
| `email(address)`                            | `email`, em minúsculas                                         | `email("marina@example.com")`            |
| `whatsapp(wa_id)`                           | `wa_id`                                                        | `whatsapp("14155550123")`                |
| `whatsapp_bsuid(user_id, business_account)` | `wa_bsuid`, com escopo da conta Business                       | `whatsapp_bsuid("bsuid.8812", "waba-1")` |
| `system_id(namespace, id, *, kind=None)`    | `system_id`; `kind="account"` ou `"partner"` para organizações | `system_id("crm", "48213")`              |
| `app_user(user_id)`                         | `app_user_id`                                                  | `app_user("u-7781")`                     |
| `anonymous(visitor_id)`                     | `anon_id`                                                      | `anonymous("visitor-31f")`               |

Todo método que recebe handle aceita também o modelo `Handle` ou um dicionário com os campos dele (`{"type": "phone_e164", "value": "+14155550123"}`). Objetos entram como `"invoice:erp:0823"` ou como `ObjectRef`.

## context()

O contexto de uma pessoa, de uma organização ou de um objeto de negócio. Corresponde a [`POST /v1/context`](/api/context).

```python theme={null}
niadra.context(
    subject: HandleLike | None = None,
    object: ObjectLike | None = None,
    *,
    about: HandleLike | None = None,
    view: str = "chat",
    verification: str = "V0",
    conversation_id: str | None = None,
    task_id: str | None = None,
    query: str | None = None,
    delta: bool = False,
    target: str | TargetModel | None = None,
    timeout: float | None = None,
    use_cache: bool = True,
) -> Context
```

| Parâmetro         | Tipo                   | Padrão        | Descrição                                                                                                   |
| ----------------- | ---------------------- | ------------- | ----------------------------------------------------------------------------------------------------------- |
| `subject`         | handle                 | `None`        | De quem é o contexto. Passe `subject` ou `object`, exatamente um.                                           |
| `object`          | `str` ou `ObjectRef`   | `None`        | Centra o contexto num objeto de negócio, como `"invoice:erp:0823"`.                                         |
| `about`           | handle                 | `None`        | A conta ou o parceiro em nome de quem a pessoa age. Exige vínculo ativo.                                    |
| `view`            | `str`                  | `"chat"`      | `voice`, `chat`, `brief`, `full`, `custom`, `account`, `partner` ou uma view de tarefa como `task:billing`. |
| `verification`    | `str`                  | `"V0"`        | O que a conversa provou, de `V0` a `V4`, ou `no_customer` para agente interno.                              |
| `conversation_id` | `str`                  | `None`        | Fixa o contexto na conversa e liga o cache.                                                                 |
| `task_id`         | `str`                  | `None`        | O mesmo, para a tarefa de um agente interno.                                                                |
| `query`           | `str`                  | `None`        | O assunto do turno. Uma leitura com `query` é avulsa e nunca fica fixada.                                   |
| `delta`           | `bool`                 | `False`       | Pede o que mudou desde a última leitura deste agente. Nunca sai do cache.                                   |
| `target`          | `str` ou `TargetModel` | `None`        | O modelo que vai ler o contexto, como `"openai/gpt-4.1"`, para mirar o piso de cache dele.                  |
| `timeout`         | `float`                | de `Timeouts` | O tempo máximo desta chamada, em segundos.                                                                  |
| `use_cache`       | `bool`                 | `True`        | `False` pula o cache nesta chamada.                                                                         |

Devolve um `Context`: todos os campos da resposta da API mais o que o SDK sabe da chamada.

| Campo                      | Descrição                                                                                                                          |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `text`                     | O contexto.                                                                                                                        |
| `system_block`             | O contexto fixado, como texto; vazio quando não há. Vai depois das suas instruções.                                                |
| `turn_block`               | O delta e os turnos ao vivo de outros canais, marcados para o modelo. Vai no fim do prompt.                                        |
| `withheld`                 | Itens que a política reteve neste nível de verificação.                                                                            |
| `verification`             | `requested`, `effective` e o `reason` quando o efetivo é menor.                                                                    |
| `etag`, `version`, `as_of` | Identidade e atualidade do contexto.                                                                                               |
| `live`, `delta`            | Turnos recentes de outros canais, e o que mudou.                                                                                   |
| `path`, `is_holdout`       | Qual camada de leitura respondeu; `holdout` quer dizer que o perfil está no grupo de controle e o contexto vem vazio de propósito. |
| `origin`                   | `network`, `cache`, `stale`, `last_good` ou `empty`.                                                                               |
| `error`, `elapsed_ms`      | Por que o contexto veio vazio, quando veio, e quanto a chamada levou.                                                              |

```python theme={null}
from niadra import Niadra, phone

niadra = Niadra()
ctx = niadra.context(phone("+14155550123"), view="voice", verification="V1", conversation_id="call-4471")

system_prompt = f"{AGENT_INSTRUCTIONS}\n\n{ctx.system_block}"
if ctx.turn_block:
    messages.append({"role": "system", "content": ctx.turn_block})
```

## search()

Busca no histórico inteiro de um sujeito, por palavra e por significado. Corresponde a [`POST /v1/history/search`](/api/history-search).

```python theme={null}
niadra.search(
    subject: HandleLike,
    query: str,
    *,
    about: HandleLike | None = None,
    filters: HistoryFilters | Mapping | None = None,
    max_tokens: int = 800,
    verification: str = "V0",
    conversation_id: str | None = None,
    task_id: str | None = None,
    voice: bool = False,
    timeout: float | None = None,
) -> SearchResult
```

| Parâmetro                    | Tipo                           | Padrão      | Descrição                                                                      |
| ---------------------------- | ------------------------------ | ----------- | ------------------------------------------------------------------------------ |
| `subject`                    | handle                         | obrigatório | De quem é o histórico.                                                         |
| `query`                      | `str`                          | obrigatório | Linguagem natural ou palavras, até 2.000 caracteres.                           |
| `about`                      | handle                         | `None`      | A organização em nome de quem a pessoa age.                                    |
| `filters`                    | `HistoryFilters` ou dicionário | `None`      | `since`, `until`, `channels`, `categories`, `item_kinds`, `outcome`, `object`. |
| `max_tokens`                 | `int`                          | `800`       | Orçamento da resposta, de 50 a 4.000. Use 300 na voz.                          |
| `verification`               | `str`                          | `"V0"`      | O mesmo nível do contexto.                                                     |
| `conversation_id`, `task_id` | `str`                          | `None`      | Quem pergunta, para o comprovante e para a verificação da sessão.              |
| `voice`                      | `bool`                         | `False`     | Usa o tempo máximo da voz.                                                     |

Devolve um `SearchResult` com `items`, `recurrence` (quantas vezes a mesma categoria aconteceu e como terminou a última), `withheld`, `as_of`, `tokens_used`, `degraded` e, em caso de falha, `error`.

```python theme={null}
found = niadra.search(phone("+14155550123"), "credit for missed technician visit", max_tokens=300, voice=True)
if found.recurrence:
    print(found.recurrence.occurrences, found.recurrence.last_resolution)
```

## timeline()

Uma página do histórico do sujeito, do mais recente para o mais antigo. Corresponde a [`POST /v1/history/timeline`](/api/history-timeline).

```python theme={null}
niadra.timeline(
    subject: HandleLike,
    *,
    about: HandleLike | None = None,
    filters: HistoryFilters | Mapping | None = None,
    cursor: str | None = None,
    limit: int = 20,
    verification: str = "V0",
    conversation_id: str | None = None,
    voice: bool = False,
    timeout: float | None = None,
) -> TimelinePage
```

`limit` vai de 1 a 100. Devolve uma `TimelinePage` com `items`, `next_cursor`, `withheld`, `as_of` e, em caso de falha, `error`. Passe `next_cursor` como `cursor` para continuar.

```python theme={null}
page = niadra.timeline(phone("+14155550123"), filters={"since": "2026-01-01T00:00:00Z"})
for item in page.items:
    print(item.at, item.kind, item.text)
```

## open()

Abre um episódio ou objeto encontrado por `search()` ou `timeline()`. Corresponde a [`GET /v1/history/items/{item_id}`](/api/history-item).

```python theme={null}
niadra.open(
    item_id: str,
    *,
    verification: str = "V0",
    conversation_id: str | None = None,
    task_id: str | None = None,
    voice: bool = False,
    timeout: float | None = None,
) -> OpenedItem | None
```

Devolve um `OpenedItem` (`summary`, `requested`, `promises`, `outcome`, `resolution`, `derived`, `timeline`) ou `None` quando ele não está disponível. O trecho literal, `excerpt`, só volta para chaves com escopo elevado.

```python theme={null}
item = niadra.open("ep_01J2", conversation_id="call-4471")
if item:
    print(item.summary, item.resolution)
```

## object\_state() e object\_timeline()

Leituras de um objeto de negócio. Correspondem a [`GET /v1/objects/{object_type}/{namespace}/{external_id}`](/api/object) e à [linha do tempo](/api/object-timeline) dele.

```python theme={null}
niadra.object_state(object: ObjectLike, *, voice: bool = False, timeout: float | None = None) -> ObjectState | None

niadra.object_timeline(
    object: ObjectLike,
    *,
    cursor: str | None = None,
    limit: int = 20,
    voice: bool = False,
    timeout: float | None = None,
) -> ObjectTimeline | None
```

`ObjectState` traz `ref`, `state`, `as_of`, `source_id`, `record_ref` e as `open_items`. O estado vem só do que os sistemas de registro informaram; a ação de um agente conta quando um sistema a confirma. `ObjectTimeline` traz `ref`, `items`, `next_cursor` e `as_of`: eventos de sistema e ações de agente, do mais recente para o mais antigo, nunca o que alguém disse. Ids de objeto são ids de registro, não dado pessoal, então vão na URL; um id com barra não pode ser endereçado assim.

```python theme={null}
invoice = niadra.object_state("invoice:erp:0823")
page = niadra.object_timeline("invoice:erp:0823", limit=20)
```

## track()

Enfileira uma mensagem, um evento de sistema, uma ação ou qualquer outro item de lote, e volta na hora. Corresponde a [`POST /v1/batch`](/api/batch).

```python theme={null}
niadra.track(item: EventItem | Mapping) -> bool
```

Aceita um `EventItem` ou um dicionário com os campos dele. Um dicionário sem `channel` recebe o canal padrão do cliente; `idempotency_key` (um UUIDv7) e `occurred_at` (agora) são preenchidos quando você não passa. Use o id da mensagem no provedor como `idempotency_key` quando houver, para um lote repetido nunca duplicar nada. Devolve `False` quando o item foi descartado: inválido, impossível de serializar, fila cheia ou cliente desligado.

```python theme={null}
niadra.track({
    "channel": "whatsapp",
    "conversation_id": "wa-8812",
    "idempotency_key": "wamid.HBgLMTQxNTU1NTAxMjMVAgASGBQzQUQ",
    "handles": [phone("+14155550123")],
    "speaker": {"role": "customer"},
    "content": {"text": "The technician never showed up. I am calling you."},
})
```

## action()

Registra o que um agente fez num sistema de registro. Vai para a fila, como `track()`.

```python theme={null}
niadra.action(
    operation: str,
    *,
    subject: HandleLike | None = None,
    object: ObjectLike | None = None,
    result: str | None = None,
    purpose: str | None = None,
    closes: str | Closes | Mapping | None = None,
    conversation_id: str | None = None,
    task_id: str | None = None,
    channel: str | None = None,
    speaker: str = "ai_agent",
    speaker_id: str | None = None,
    corrects_action_id: str | None = None,
    occurred_at: datetime | None = None,
    idempotency_key: str | None = None,
    context_stamp: ContextStamp | Mapping | None = None,
) -> bool
```

| Parâmetro            | Descrição                                                                                            |
| -------------------- | ---------------------------------------------------------------------------------------------------- |
| `operation`          | A operação canônica, como `credit` ou `reschedule`.                                                  |
| `subject`, `object`  | De quem e do que a ação trata.                                                                       |
| `result`             | O que aconteceu, até 2.000 caracteres.                                                               |
| `closes`             | A pendência que a ação cumpre: o id da pendência como texto, ou `{"object": ..., "operation": ...}`. |
| `corrects_action_id` | Ação é imutável; a correção é uma ação nova que aponta para a anterior.                              |
| `context_stamp`      | Com qual contexto o agente agiu. Conversas e tarefas preenchem sozinhas depois de `mark_injected()`. |

A ação fica `declared` até o sistema de registro confirmar com o próprio evento. Registrar ação exige o escopo `act` na chave.

```python theme={null}
niadra.action(
    "credit",
    subject=system_id("crm", "48213"),
    object="invoice:erp:0823",
    result="$40 credit on the August bill",
    closes={"object": {"type": "invoice", "namespace": "erp", "id": "0823"}, "operation": "dispute"},
    channel="erp",
    task_id="billing-7741",
)
```

## identify()

Afirma que dois ou mais handles são do mesmo sujeito. Sai na hora, sem fila, para o `context()` seguinte enxergar; se a requisição falhar, o item vai para a fila de envio e o método devolve `None`.

```python theme={null}
niadra.identify(
    handles: Sequence[HandleLike],
    *,
    method: str = "explicit_identify",
    subject_kind: str = "person",
    conversation_id: str | None = None,
) -> BatchResponse | None
```

```python theme={null}
niadra.identify([phone("+14155550123"), email("marina@example.com")], conversation_id="wa-8812")
```

## verify()

Eleva o nível de verificação de uma conversa ou tarefa, depois que você provou. A Niadra nunca deduz o nível. Sai na hora, e o contexto guardado daquela conversa é descartado, para o próximo `context()` já vir no nível novo.

```python theme={null}
niadra.verify(
    method: str,
    level: str,
    *,
    handle: HandleLike,
    conversation_id: str | None = None,
    task_id: str | None = None,
    valid_until: datetime | None = None,
) -> BatchResponse | None
```

`method` é `otp_whatsapp`, `otp_sms`, `login`, `kba`, `network_attestation` ou `human_agent`. Um nível acima do teto da fonte volta como erro de item `verification_not_allowed`.

```python theme={null}
niadra.verify("otp_whatsapp", "V3", handle=phone("+14155550123"), conversation_id="wa-8812")
```

## feedback()

Corrige o que a Niadra derivou sobre um sujeito. Sai na hora e vira evento, então é auditado como qualquer outro. Corresponde a [`POST /v1/feedback`](/api/feedback).

```python theme={null}
niadra.feedback(
    action: str,
    subject: HandleLike,
    *,
    fact_id: str | None = None,
    open_item_id: str | None = None,
    conversation_id: str | None = None,
    value: str | None = None,
    reason: str | None = None,
    idempotency_key: str | None = None,
) -> BatchResponse | None
```

Cada ação pede os próprios campos:

| `action`               | Exige                                   |
| ---------------------- | --------------------------------------- |
| `retract_fact`         | `fact_id`. O fato sai de todo contexto. |
| `correct_fact`         | `fact_id` e `value`, o valor certo.     |
| `resolve_open_item`    | `open_item_id`.                         |
| `conversation_outcome` | `conversation_id` e `value`.            |

`value` vai até 2.000 caracteres e `reason` até 500. Devolve `None` quando a correção não pôde ser entregue.

```python theme={null}
niadra.feedback("resolve_open_item", phone("+14155550123"), open_item_id="oi_01J8ZK", reason="Visit rescheduled")
niadra.feedback("conversation_outcome", phone("+14155550123"), conversation_id="call-4471", value="resolved")
```

## upload\_media()

Entrega um arquivo à Niadra, como a gravação de uma ligação, e devolve a referência para o evento. Mídia nunca viaja dentro do evento. Corresponde a [`POST /v1/media/uploads`](/api/media-uploads).

```python theme={null}
niadra.upload_media(
    data: bytes | bytearray | memoryview,
    content_type: str,
    *,
    subject: HandleLike | None = None,
) -> MediaUpload | None
```

O método calcula o hash dos bytes, reserva o upload e manda os bytes direto para o armazenamento pela URL assinada, de vida curta. Ele envia exatamente os `upload_headers` que a API devolveu, que são os cabeçalhos cobertos pela assinatura, e nada além: nunca a sua chave. A URL precisa ser HTTPS, a não ser no emulador local. O armazenamento confere o corpo contra o tamanho e o hash declarados. Com `subject`, o arquivo fica guardado sob aquela pessoa, então apagar a pessoa apaga o arquivo, mesmo que nenhum evento o cite.

Devolve um `MediaUpload` com `media_ref`, `media_sha256`, `content_type`, `size_bytes` e `expires_at`, ou `None` quando o upload falhou. Cada tentativa da transferência tem o tempo máximo `upload`, 60 s por padrão.

```python theme={null}
upload = niadra.upload_media(recording, "audio/wav", subject=phone("+14155550123"))
if upload:
    niadra.track({
        "channel": "voice",
        "conversation_id": "call-4471",
        "handles": [phone("+14155550123")],
        "speaker": {"role": "customer"},
        "content": {
            "type": "audio",
            "media_ref": upload.media_ref,
            "media_sha256": upload.media_sha256,
            "transcript": transcript,
        },
    })
```

## handoff()

Registra uma transferência para um humano (`"human"`) ou para outro agente (`"agent"`). Vai para a fila. A medição do aproveitamento do contexto lê esse registro para contar transferências em que o destino não leu o contexto.

```python theme={null}
niadra.handoff(
    conversation_id: str,
    target: str,
    *,
    target_source: str | None = None,
    reason: str | None = None,
    mode: str = "warm",
) -> bool
```

## conversation()

Uma conversa com um cliente, usada como gerenciador de contexto. Sair do bloco emite `conversation.ended`, mesmo quando o bloco levantou exceção.

```python theme={null}
niadra.conversation(
    conversation_id: str | None = None,
    *,
    subject: HandleLike | None = None,
    object: ObjectLike | None = None,
    about: HandleLike | None = None,
    channel: str | None = None,
    view: str = "chat",
    verification: str = "V0",
    target: str | TargetModel | None = None,
    agent_id: str | None = None,
) -> Conversation
```

Sem `conversation_id`, o SDK cria um. `channel` vem do cliente quando não é passado. `agent_id` identifica o seu agente dentro da fonte e vai carimbado nos turnos e nas ações dele.

| Método                                                    | Descrição                                                                                                     |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `context(**overrides)`                                    | O contexto deste turno: os bytes fixados, com cada delta desde a fixação em `delta`.                          |
| `mark_injected(context=None, *, at=None)`                 | Registra que o contexto entrou no prompt. Os próximos turnos e ações do agente levam isso em `context_stamp`. |
| `customer(text, **event)`                                 | Registra o que o cliente disse.                                                                               |
| `agent(text, **event)`                                    | Registra a resposta do agente, com o carimbo do contexto.                                                     |
| `human_agent(text, **event)`                              | Registra um turno de atendente humano.                                                                        |
| `action(operation, **options)`                            | `action()` com sujeito, objeto, canal, ids e carimbo já preenchidos.                                          |
| `verify(method, level, *, handle=None, valid_until=None)` | Eleva o nível e passa a ler no nível novo; `handle` vem do sujeito.                                           |
| `handoff(target, **options)`                              | Registra a transferência desta conversa.                                                                      |
| `tools()`                                                 | O kit do histórico amarrado a esta conversa, no nível atual; `None` sem sujeito.                              |
| `end()`                                                   | Emite `conversation.ended`. As chamadas seguintes não fazem nada.                                             |

O primeiro `context()` recebe o contexto que o servidor fixa para a conversa. As leituras seguintes pedem também o delta, e a conversa guarda cada delta que chega, em ordem, então `turn_block` leva todas as mudanças desde a fixação, seguidas dos turnos ao vivo. Quando o servidor fixa um contexto novo, depois de `verify()` por exemplo, os deltas guardados saem: o contexto novo já os contém. Uma leitura com `query=` é avulsa e não mexe neles.

Chame `mark_injected()` toda vez que puser o contexto num prompt. É assim que a Niadra distingue um contexto que chegou depois de o agente falar de um contexto que o agente tinha e não usou. A sessão guarda também `context_injected_at` e `first_agent_turn_at`, o primeiro de cada, para as suas próprias checagens.

```python theme={null}
with niadra.conversation("wa-8812", subject=phone("+14155550123"), channel="whatsapp") as conv:
    conv.customer("The technician never showed up. I am calling you.")
    ctx = conv.context()
    conv.mark_injected(ctx)
    reply = llm(system=[AGENT_INSTRUCTIONS, ctx.system_block], turn=ctx.turn_block)
    conv.agent(reply)
    conv.verify("otp_whatsapp", "V3")
    conv.handoff("human", reason="asked for a person")
```

`current_session()` devolve a conversa ou tarefa cujo bloco está rodando na thread ou task atual, se houver.

## task()

O mesmo para um agente interno (cobrança, pedidos, tickets). Emite `task.ended` na saída.

```python theme={null}
niadra.task(
    task_id: str | None = None,
    *,
    subject: HandleLike | None = None,
    object: ObjectLike | None = None,
    about: HandleLike | None = None,
    channel: str | None = None,
    view: str = "brief",
    verification: str = "V0",
    target: str | TargetModel | None = None,
    agent_id: str | None = None,
) -> Task
```

Com `object`, o contexto fica centrado nele; use uma view de tarefa como `task:billing`. Uma tarefa tem os mesmos métodos de uma conversa, menos `handoff()`.

```python theme={null}
with niadra.task("billing-7741", object="invoice:erp:0823", view="task:billing",
                 verification="no_customer", channel="erp") as task:
    ctx = task.context()
    task.mark_injected(ctx)
    task.action(
        "credit",
        result="$40 credit on the August bill",
        closes={"object": {"type": "invoice", "namespace": "erp", "id": "0823"}, "operation": "dispute"},
    )
```

## tools()

O kit do histórico como ferramentas de chamada de função, amarrado a um cliente. O cliente fica amarrado aqui, fora do alcance do modelo: o modelo escolhe a consulta, nunca o perfil, e é isso que impede uma injeção de prompt de trocar de cliente.

```python theme={null}
niadra.tools(
    subject: HandleLike,
    *,
    about: HandleLike | None = None,
    conversation_id: str | None = None,
    task_id: str | None = None,
    verification: str = "V0",
    voice: bool = False,
) -> ToolKit
```

| Membro                    | Descrição                                                                                                                                            |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `definitions`             | As três ferramentas, `search_customer_history`, `get_customer_timeline` e `open_history_item`, no formato `{"type": "function", "function": {...}}`. |
| `names`                   | Os nomes delas.                                                                                                                                      |
| `anthropic_definitions()` | As mesmas ferramentas com `name`, `description` e `input_schema`.                                                                                    |
| `call(name, arguments)`   | Executa uma chamada de ferramenta e devolve o texto para o modelo: JSON compacto, ou um erro curto que diz ao modelo para seguir em frente.          |

`AsyncNiadra.tools()` devolve um `AsyncToolKit`, com `call()` aguardado. As definições também saem em [`GET /v1/history/tools`](/api/history-tools).

```python theme={null}
kit = niadra.tools(phone("+14155550123"), conversation_id="call-4471")
reply = llm.chat.completions.create(model=MODEL, messages=messages, tools=kit.definitions)
for call in reply.choices[0].message.tool_calls or []:
    output = kit.call(call.function.name, call.function.arguments)
    messages.append({"role": "tool", "tool_call_id": call.id, "content": output})
```

## subject\_token()

Emite um token assinado, de 15 minutos, que amarra uma conexão MCP a um cliente. Chame do seu backend. Corresponde a [`POST /v1/subject-tokens`](/api/subject-tokens).

```python theme={null}
niadra.subject_token(
    subject: HandleLike,
    *,
    about: HandleLike | None = None,
    conversation_id: str | None = None,
    task_id: str | None = None,
    verification: str = "V0",
) -> SubjectToken | None
```

Devolve um `SubjectToken` com `token`, `expires_at` e `headers`, o cabeçalho `Niadra-Subject-Token` que vai junto da chave da fonte quando o agente abre a conexão com `mcp_url`. Com `about`, a organização fica amarrada como o sujeito. Veja [MCP com qualquer LLM](/guides/mcp).

```python theme={null}
token = niadra.subject_token(phone("+14155550123"), conversation_id="call-4471", verification="V1")
# connect to niadra.mcp_url with the source key as Bearer and token.headers
```

## wrap()

Embrulha o cliente Python da OpenAI, ou qualquer cliente com o mesmo formato, para toda chamada dentro de um bloco de conversa ou tarefa receber o contexto e registrar a resposta.

```python theme={null}
wrap(client, *, conversation: Conversation | Task | None = None) -> client
```

Dentro de um bloco (ou para a sessão que você passar), `chat.completions.create` e `chat.completions.parse`, síncronos ou assíncronos, com streaming ou sem, recebem o contexto fixado como mensagem de sistema logo depois das suas mensagens de sistema iniciais, e o `turn_block` como mensagem de sistema no fim. A injeção é carimbada com `mark_injected()`, e a resposta do modelo é registrada como turno do agente: quando o stream termina ou é fechado e, por `with_raw_response`, quando você chama `parse()`. Fora de um bloco, as chamadas passam intactas. O embrulho devolve um proxy e nunca altera o seu cliente. Nada do que ele faz derruba a chamada ao modelo: um contexto que não chega fica de fora, e uma falha ao registrar a resposta vai para o log, sem conteúdo.

```python theme={null}
from openai import OpenAI
from niadra import Niadra, phone, wrap

niadra = Niadra(channel="whatsapp")
openai = wrap(OpenAI())

with niadra.conversation("wa-8812", subject=phone("+14155550123")) as conv:
    conv.customer(incoming_text)
    reply = openai.chat.completions.create(model="gpt-4.1", messages=messages)
```

## flush() e close()

```python theme={null}
niadra.flush(timeout: float | None = None) -> bool
niadra.close(timeout: float | None = 5.0) -> None
```

`flush()` manda tudo o que está na fila a partir da thread que chamou e devolve `True` quando não sobra nada. `close()` esvazia a fila por até `timeout` segundos e libera as conexões. No `AsyncNiadra`, os dois são aguardados.

## AsyncNiadra

`AsyncNiadra` tem os mesmos métodos. As leituras, `identify()`, `verify()`, `feedback()`, `upload_media()`, `subject_token()`, `flush()` e `close()` são aguardados; `track()`, `action()`, `handoff()`, `conversation()`, `task()` e `tools()` não. Conversas e tarefas são gerenciadores de contexto assíncronos.

```python theme={null}
from niadra import AsyncNiadra, phone

niadra = AsyncNiadra(channel="whatsapp")

async with niadra.conversation("wa-8812", subject=phone("+14155550123")) as conv:
    ctx = await conv.context()
    conv.mark_injected(ctx)
```

## Erros

Com `strict=True`, ou nos ajudantes que você chama direto, como `ApiKey.parse()` e as funções de handle, o SDK levanta:

| Exceção                    | Quando                                                                                       |
| -------------------------- | -------------------------------------------------------------------------------------------- |
| `NiadraError`              | Classe base de todas.                                                                        |
| `ConfigurationError`       | Chave ausente ou malformada, endereço inválido.                                              |
| `APIConnectionError`       | Sem resposta HTTP: DNS, TCP, TLS ou conexão derrubada.                                       |
| `APITimeoutError`          | O tempo máximo do método acabou.                                                             |
| `APIError`                 | Qualquer status de erro. `status_code`, `code`, `request_id` e `problem` (o corpo RFC 9457). |
| `BadRequestError`          | 400.                                                                                         |
| `AuthenticationError`      | 401: chave desconhecida, revogada ou malformada.                                             |
| `PermissionDeniedError`    | 403: falta escopo à chave, ou a fonte foi cortada.                                           |
| `NotFoundError`            | 404.                                                                                         |
| `ConflictError`            | 409: a mesma `Idempotency-Key` com outro corpo.                                              |
| `WrongCellError`           | 421: o espaço está mudando de célula.                                                        |
| `UnprocessableEntityError` | 422.                                                                                         |
| `RateLimitError`           | 429, com `retry_after`.                                                                      |
| `ServerError`              | 5xx.                                                                                         |

O catálogo completo de códigos está em [Erros](/errors). Cite `request_id` quando falar com o suporte.

## Emulador local

`niadra-mock`, instalado junto da biblioteca, é um emulador local, em memória, da mesma API. Nos testes, rode o SDK contra ele no mesmo processo:

```python theme={null}
import httpx
from niadra import Niadra
from niadra_mock import MOCK_KEY, MockApp

mock = MockApp()
http = httpx.Client(transport=httpx.WSGITransport(app=mock.wsgi))  # ASGITransport(app=mock.asgi) for AsyncNiadra
niadra = Niadra(MOCK_KEY, base_url="http://mock", http_client=http, strict=True)
```

Os turnos recentes viram um contexto pequeno, cada delta sai uma vez por mudança, a busca casa palavras, a verificação só sobe por `verify()`, os objetos tiram o estado dos eventos de sistema, a correção vira um evento `feedback.*` e os uploads de mídia vão para `mock.cell.media`. `mock.cell` também deixa inspecionar eventos e provocar falhas (`fail_next`, `revoke`, `cut`, `put_in_holdout`). No terminal, `niadra-mock --port 8765` serve o emulador por HTTP.

## Próximos passos

<CardGroup cols={2}>
  <Card title="SDK de TypeScript" href="/sdk/typescript">
    A mesma superfície para Node e runtimes de borda.
  </Card>

  <Card title="Início rápido" href="/quickstart">
    Da chave ao primeiro contexto entregue.
  </Card>

  <Card title="O contexto e as views" href="/concepts/context">
    O que entra no contexto e por quê.
  </Card>

  <Card title="Erros" href="/errors">
    O catálogo de códigos e o que fazer com cada um.
  </Card>
</CardGroup>
