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

# Identidade e verificação

> Handles, asserções, perfis que se unem e se separam, e os níveis V0 a V4.

A Niadra reconhece o mesmo cliente no WhatsApp, na voz, no app e no CRM. Ela faz isso sem adivinhar: cada ligação entre dois identificadores é uma asserção registrada, com método e evidência, e pode ser desfeita. Esta página explica handles, perfis, asserções e os níveis de verificação que decidem o que cada conversa pode ler.

## Handles

Um **handle** é um identificador de um sujeito num canal ou sistema. Ele tem um `type`, um `value` e, quando o tipo pede, um `scope`:

| `type`                      | O que é                                              | `scope`                   |
| --------------------------- | ---------------------------------------------------- | ------------------------- |
| `phone_e164`                | Telefone no formato E.164, como `+14155550123`       |                           |
| `wa_id`, `wa_jid`, `wa_lid` | Identificadores que o WhatsApp entrega               |                           |
| `wa_bsuid`                  | Id do usuário do WhatsApp por conta comercial        | A conta WhatsApp Business |
| `email`                     | E-mail, em minúsculas                                |                           |
| `gov_id_hmac`               | HMAC do número de documento, nunca o número em claro | O país                    |
| `app_user_id`               | O id do usuário no seu app ou site                   |                           |
| `system_id`                 | O id do sujeito num sistema de registro              | O sistema, como `crm`     |
| `org_registry_hmac`         | HMAC do registro de uma empresa                      | O país                    |
| `email_domain`              | Domínio de e-mail de uma organização, só como pista  |                           |
| `anon_id`                   | Visitante ou aparelho ainda sem identificação        |                           |

O valor é classificado pelo formato, nunca pelo nome do campo de onde veio, e normalizado no servidor. Os SDKs têm funções que montam cada tipo:

<CodeGroup>
  ```python Python theme={null}
  from niadra import phone, email, whatsapp, whatsapp_bsuid, system_id, app_user, anonymous

  phone("+1 415 555 0123")          # phone_e164: +14155550123
  system_id("crm", "48213")         # system_id no namespace crm
  whatsapp_bsuid("US.1a2b3c", "waba-771")
  ```

  ```typescript TypeScript theme={null}
  import { handles } from "@niadra/sdk";

  handles.phone("+14155550123");
  handles.systemId("48213", "crm");
  handles.waBsuid("US.1a2b3c", "waba-771");
  ```
</CodeGroup>

Todo handle pertence a um tipo de sujeito, em `subject_kind`: `person` (o padrão), `account` ou `partner`. `org_registry_hmac` e `email_domain` já nascem de organização. Veja [Contas e parceiros](/concepts/accounts).

## Perfis e asserções

Um **perfil** é o cliente como a memória o enxerga: o conjunto de handles ligados entre si. Cada ligação é uma **asserção** num registro imutável, com o método que a produziu:

| Método              | Quando acontece                                                       |
| ------------------- | --------------------------------------------------------------------- |
| `explicit_identify` | Você afirmou, por `identify()`                                        |
| `otp`, `login`      | O cliente provou a posse do handle                                    |
| `system_import`     | Carga inicial a partir do seu CRM ou de um arquivo                    |
| `same_event`        | Dois handles fortes chegaram no mesmo evento, como telefone e `wa_id` |
| `external_resolver` | O seu sistema de identidade já resolveu e mandou o resultado          |
| `declared`          | O cliente disse; vale pouco até ser confirmado                        |

Quando dois handles do mesmo evento resolvem para perfis diferentes, a união acontece fora da resposta do lote, com trava. A memória fica presa ao handle de origem de cada fato, então separar dois perfis unidos por engano é uma operação de verdade: os fatos, os pedidos e as faturas voltam sozinhos para o dono certo.

<CodeGroup>
  ```python Python theme={null}
  niadra.identify(
      [phone("+14155550123"), system_id("crm", "48213")],
      method="system_import",
  )
  ```

  ```typescript TypeScript theme={null}
  await niadra.identify({
    handles: [handles.phone("+14155550123"), handles.systemId("48213", "crm")],
    method: "system_import",
  });
  ```

  ```bash cURL theme={null}
  curl -X POST "https://acme-prod.us-east-1.api.niadra.com/v1/batch" \
    -H "Authorization: Bearer $NIADRA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"items": [{"type": "identify", "idempotency_key": "idf-48213", "method": "system_import",
         "handles": [{"type": "phone_e164", "value": "+14155550123"}, {"type": "system_id", "value": "48213", "scope": "crm"}],
         "occurred_at": "2026-09-22T17:00:00Z"}]}'
  ```
</CodeGroup>

`identify()` sai na hora, fora da fila, para o próximo `context()` já enxergar o perfil unido.

## Proteções contra união errada

* Dois perfis que já têm handle com nível V3 ou mais nunca se unem sozinhos; só por união forçada, com ator e motivo.
* Valores de teste e marcadores de QA ficam bloqueados por tipo, numa lista fixa que você pode estender.
* Cada perfil tem limite por tipo: um documento por país, um `system_id` por namespace, poucos telefones. Ao estourar, o handle mais fraco é rebaixado, e a ingestão nunca é recusada.
* Handle que liga muitos perfis em pouco tempo (tablet de loja, número de PABX, WhatsApp de atendimento) vira `ambiguous`: serve para leitura, não para novas uniões.
* Troca de número ou de BSUID cria um alias, nunca uma união automática.
* Pessoa e organização nunca se unem. O que liga as duas é um vínculo.

Handle desconhecido, parcial (um BSUID ainda sem telefone) ou ambíguo recebe um perfil provisório e um contexto vazio. Na dúvida, a memória não revela nada.

## Governar a identidade

O seu time corrige a identidade pelas rotas de governança, com uma pessoa do Console que tenha o papel `security` ou `integration`, ou com uma chave de fonte de escopo `admin`. Toda escrita exige o cabeçalho `Idempotency-Key`, responde **202** com o id da operação e é aplicada sob a trava do perfil, em ordem:

| O quê                                       | Rota                                                                                                                               |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Ver por que dois handles são um perfil só   | [`GET /v1/identity/assertions`](/api/identity-assertions), filtrando por `profile_id` e `state` (`applied`, `pending`, `rejected`) |
| Afirmar que handles são do mesmo sujeito    | [`POST /v1/identity/assertions`](/api/identity-assertions-create), com `handles`, `method` e `reason`                              |
| Desfazer uma ligação errada                 | [`POST /v1/identity/assertions/{assertion_id}/retract`](/api/identity-assertion-retract)                                           |
| Forçar uma união que as proteções recusaram | [`POST /v1/identity/merge`](/api/identity-merge), com `profile_ids`, `force` e `reason`                                            |
| Separar um perfil                           | [`POST /v1/identity/unmerge`](/api/identity-unmerge), com `profile_id` e os `handle_ids` ou `assertion_ids` a separar              |
| Impedir que um valor ligue perfis           | [`POST /v1/identity/handles/block`](/api/identity-handles-block)                                                                   |

A Niadra também propõe ligações que não consegue provar, como dois perfis com o mesmo nome e um e-mail parecido. Ela nunca aplica sozinha: [`GET /v1/identity/suggestions`](/api/identity-suggestions) lista as sugestões com um `score` e os sinais que o sustentam, [aceitar](/api/identity-suggestion-accept) transforma uma delas numa asserção `accepted_suggestion`, e [descartar](/api/identity-suggestion-dismiss) encerra a sugestão.

Para achar um perfil a partir de um handle, use [`POST /v1/profiles/search`](/api/profiles-search): o valor vai no corpo e nunca entra em log. [`GET /v1/profiles/{profile_id}`](/api/profile) devolve os handles, os vínculos, o tipo de sujeito e o pseudônimo, com a máscara do papel de quem lê.

## Níveis de verificação

O mesmo cliente recebe contextos diferentes conforme o quanto a conversa provou quem ele é:

| Nível         | Significado                 | Exemplo                                                           |
| ------------- | --------------------------- | ----------------------------------------------------------------- |
| `V0`          | Autodeclarado               | "Sou a Marina" num chat anônimo                                   |
| `V1`          | Plausível pelo canal        | Chegou daquele número, sem atestado forte                         |
| `V2`          | Atestado pelo canal         | Chamada com atestado de rede A, remetente autenticado do WhatsApp |
| `V3`          | Desafiado                   | OTP no mesmo canal ou login no seu app                            |
| `V4`          | Documental ou por atendente | Conferido contra o sistema de registro ou por uma pessoa          |
| `no_customer` | Tarefa sem cliente presente | Agente interno de cobrança                                        |

A sua política diz, por categoria de memória, o nível mínimo. O contexto informa em `withheld` quantos itens ficaram retidos: o agente sabe que verificar libera mais, sem ver o conteúdo. `no_customer` fica fora da escala e só é aceito de fontes com audiência `internal_agent`; esse contexto nunca vai ao cliente final.

## Nível efetivo

O nível que vale é o menor entre três: o que você pediu, o **teto da fonte** e o que a conversa provou. O teto é atributo da fonte: agente automatizado V2, fonte que executa OTP V3, V4 só para mesa humana ou sistema de registro. Uma chave de agente vazada não consegue declarar V4.

A resposta do contexto mostra os dois lados em `verification`: `requested`, `effective` e, quando o efetivo é menor, `reason` (`source_ceiling` ou `not_proven`).

## Elevar o nível

O nível sobe por um evento `verify`, nunca por inferência. Ele vale para aquela conversa ou tarefa, com validade opcional. Depois dele, peça um `context()` novo: o SDK já descarta o contexto guardado daquela conversa.

<CodeGroup>
  ```python Python theme={null}
  niadra.verify(
      "otp_whatsapp",
      "V3",
      handle=phone("+14155550123"),
      conversation_id="wa-8812",
  )
  ctx = niadra.context(subject=phone("+14155550123"), verification="V3", conversation_id="wa-8812")
  ```

  ```typescript TypeScript theme={null}
  await niadra.verify({
    handle: handles.phone("+14155550123"),
    method: "otp_whatsapp",
    level: "V3",
    conversation_id: "wa-8812",
  });
  const ctx = await niadra.context({ subject: handles.phone("+14155550123"), verification: "V3", conversation_id: "wa-8812" });
  ```
</CodeGroup>

Os métodos são `otp_whatsapp`, `otp_sms`, `login`, `kba`, `network_attestation` e `human_agent`. Um `verify` acima do teto da fonte volta no 207 com o código `verification_not_allowed`. Em voz, o atestado de rede da chamada (STIR/SHAKEN e equivalentes) mapeia A para V2, e B e C para V1.

## Próximos passos

<CardGroup cols={2}>
  <Card title="O contexto e as views" href="/concepts/context">
    o que cada nível libera no contexto.
  </Card>

  <Card title="Contas e parceiros" href="/concepts/accounts">
    organizações e vínculos com papel.
  </Card>

  <Card title="Listar asserções" href="/api/identity-assertions">
    a API do registro de identidade.
  </Card>

  <Card title="Sugestões de identidade" href="/api/identity-suggestions">
    ligações para revisar, nunca aplicadas sozinhas.
  </Card>
</CardGroup>
