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

# Criar verificação

> POST /public/verifications, o corpo, a resposta, a idempotência por ref_id e os onze códigos de resposta.

Cria uma verificação e emite a entrada da jornada. É a única chamada que o seu servidor faz para
começar uma verificação.

A chamada resolve a conta e a integração pela chave, fixa a configuração e a origem naquele
instante, e devolve uma credencial de curta duração. É por isso que ela nasce no seu backend: a
chave de API nunca chega ao browser nem ao app.

```bash theme={null}
curl -X POST https://api.legitimuz.com/public/verifications \
  -H "X-API-Key: $LEGITIMUZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "schema_version": "1.0",
    "ref_id": "pedido-4471",
    "document": { "type": "cpf", "number": "<CPF_DO_TITULAR>" },
    "integration_origin_public_id": "<ORIGIN_PUBLIC_ID>"
  }'
```

## Autenticação

<ParamField header="X-API-Key" type="string" required>
  A chave da integração, no formato `lz_...`. Criada em
  [Integrações → Segurança → Chaves de API](/platform/tokens).

  A chave não é aceita no corpo nem na query. Veja [autenticação](/start/authentication).
</ParamField>

<ParamField header="Content-Type" type="string" required>
  `application/json`. Qualquer outro valor responde `415`.
</ParamField>

<ParamField header="traceparent" type="string">
  Propaga tracing distribuído. Opcional.
</ParamField>

## Corpo

<ParamField body="schema_version" type="string" required>
  A versão do contrato. Hoje, `"1.0"`.
</ParamField>

<ParamField body="document" type="object" required>
  O documento que identifica o titular.

  <Expandable title="campos">
    <ParamField body="document.type" type="string" required>
      Hoje só `"cpf"`.
    </ParamField>

    <ParamField body="document.number" type="string" required>
      Normalizado e validado no servidor. Nunca volta na resposta nem no bootstrap do widget.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="integration_origin_public_id" type="string" required>
  A origem onde o fluxo vai abrir, um domínio web ou um app bundle. Precisa pertencer à integração
  e estar **verificada**, senão a chamada responde `422`.

  O canal (web, iOS, Android) é derivado do tipo da origem; não existe campo `channel`.
</ParamField>

<ParamField body="ref_id" type="string">
  A sua chave para essa verificação, número do pedido, id do cadastro. Até 120 caracteres, único
  por conta.

  É o que volta no webhook de desfecho. Sem ele, você precisa guardar o `public_id` da plataforma
  para saber de quem é o evento.
</ParamField>

<ParamField body="flow_public_id" type="string">
  O fluxo a usar. Ausente, a integração usa o fluxo publicado padrão dela. Precisa pertencer à
  mesma conta e integração.
</ParamField>

## Resposta

<ResponseField name="schema_version" type="string">
  A versão do contrato.
</ResponseField>

<ResponseField name="verification" type="object">
  <Expandable title="campos">
    <ResponseField name="verification.public_id" type="string">
      O identificador da verificação. Não é segredo: use-o em log e em chamado de suporte.
    </ResponseField>

    <ResponseField name="verification.status" type="string">
      Um dos cinco [status](/api/verification-status). Numa criação, `not_opened`.
    </ResponseField>

    <ResponseField name="verification.expires_at" type="string">
      Quando a jornada deixa de poder ser aberta. ISO 8601 em UTC.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="entry" type="object">
  Como o titular entra na jornada. União discriminada por `kind`, derivado da origem.

  <Expandable title="campos">
    <ResponseField name="entry.kind" type="string">
      `"web"` ou `"native"`.
    </ResponseField>

    <ResponseField name="entry.url" type="string">
      Só em `kind: "web"`. A URL da jornada, com a credencial no fragmento. Gere o QR code a partir
      dela se quiser entrega por celular.
    </ResponseField>

    <ResponseField name="entry.access_credential" type="string">
      Só em `kind: "native"`. A credencial que o seu app entrega ao SDK.
    </ResponseField>
  </Expandable>
</ResponseField>

<CodeGroup>
  ```json title="Origem web" theme={null}
  {
    "schema_version": "1.0",
    "verification": {
      "public_id": "019f0000-0000-7000-8000-000000000000",
      "status": "not_opened",
      "expires_at": "2026-09-15T14:30:00.000Z"
    },
    "entry": {
      "kind": "web",
      "url": "https://verify.legitimuz.com/#v=<access_credential>"
    }
  }
  ```

  ```json title="Origem nativa" theme={null}
  {
    "schema_version": "1.0",
    "verification": {
      "public_id": "019f0000-0000-7000-8000-000000000000",
      "status": "not_opened",
      "expires_at": "2026-09-15T14:30:00.000Z"
    },
    "entry": {
      "kind": "native",
      "access_credential": "<access_credential>"
    }
  }
  ```
</CodeGroup>

<Warning>
  A `entry.url` carrega a credencial da jornada. Trate-a como segredo de curta duração: ela sai do
  seu backend para o titular daquela verificação e para mais ninguém. Não a registre em log, não a
  mande por e-mail e não a fixe no código do cliente.
</Warning>

## Idempotência

A idempotência é por `ref_id`, no escopo da conta. Você não envia header de idempotência.

| Chamada                         | Resposta                                                        |
| ------------------------------- | --------------------------------------------------------------- |
| `ref_id` novo                   | `201` com a verificação criada                                  |
| Mesmo `ref_id`, mesmo corpo     | `200` com a verificação existente, o `status` pode ter avançado |
| Mesmo `ref_id`, corpo diferente | `409 E_REF_ID_CONFLICT`                                         |

Isso torna a chamada segura para retentativa: um timeout de rede não cria duas verificações para o
mesmo pedido.

## Códigos de resposta

| HTTP  | Código                     | Quando                                                                      |
| ----- | -------------------------- | --------------------------------------------------------------------------- |
| `201` | —                          | Verificação criada.                                                         |
| `200` | —                          | Repetição idempotente recuperou a existente.                                |
| `400` | `E_VALIDATION_FAILURE`     | Forma do corpo, CPF ou campo inválido.                                      |
| `401` | `E_UNAUTHORIZED_ACCESS`    | Chave ausente, inválida, expirada ou revogada.                              |
| `403` | `E_FORBIDDEN`              | Chave válida, sem permissão de criar verificação.                           |
| `404` | `E_NOT_FOUND`              | Fluxo ou origem fora do alcance da conta.                                   |
| `409` | `E_REF_ID_CONFLICT`        | Mesmo `ref_id`, corpo diferente.                                            |
| `415` | `E_UNSUPPORTED_MEDIA_TYPE` | `Content-Type` não é `application/json`.                                    |
| `422` | `E_ORIGIN_NOT_VERIFIED`    | A origem existe, mas não foi verificada.                                    |
| `429` | `E_RATE_LIMITED`           | Teto da chave ou da integração. Veja [limites](/api/errors#limites-de-uso). |
| `500` | `E_INTERNAL_SERVER_ERROR`  | Falha inesperada.                                                           |
| `503` | `E_AUDIT_UNAVAILABLE`      | Escrita obrigatória de auditoria indisponível. Tente de novo.               |

O formato do corpo de erro está em [erros da API](/api/errors).

## Próximo passo

<Columns cols={2}>
  <Card title="Abrir o fluxo" icon="window-maximize" href="/guides/first-verification">
    O que fazer com a `entry` no browser ou no app.
  </Card>

  <Card title="Receber a decisão" icon="broadcast" href="/webhooks/events#verificationdecided">
    O evento que fecha o ciclo, com o desfecho no corpo.
  </Card>
</Columns>
