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

# Comprovar um endereço

> Dois fluxos chegam na caixa de entrada e são as únicas coisas capazes de comprovar um e-mail: o magic link e o código de seis dígitos.

O `/v1/boot` comprova o `external_id` e nada mais — um e-mail que vem junto é
guardado como atributo, nunca como aresta de identidade. Os dois fluxos desta
página são os que comprovam o endereço em si, porque chegam *nele*. Cada um
responde com uma sessão `uk_ct_…` verificada, marca `email_verified` no contato e
publica `contact.email_verified`.

São superfície pública alcançada com uma **chave publicável** no corpo da
requisição, a partir do seu próprio domínio, e mantêm a postura mais estrita da
API: `202` incondicional no pedido, um só `401` para todo jeito de um resgate
falhar, limites estritos por IP e tokens de uso único para tudo que chega por
e-mail.

<Note>
  Nenhum dos dois cria conta. O endereço precisa já pertencer a um contato — criado
  pelo seu backend com [`POST /v1/contacts`](/pt-br/concepts/contacts) ou
  trazido por um boot — porque um fluxo que cunhasse contatos a partir de uma caixa
  de entrada seria uma segunda porta de cadastro ao lado da sua. O pedido continua
  respondendo `202` para um endereço que ninguém tem, então o endpoint não serve
  para descobrir quais têm.
</Note>

## Magic links

Login sem senha para uma página que não tem sessão a oferecer: o e-mail de um
ticket, a central de ajuda pública, o portal.

<Steps>
  <Step title="Pedir">
    ```bash theme={null}
    curl -s $API/v1/contact-auth/magic-link \
      -H 'Content-Type: application/json' \
      -d '{ "publishable_key": "uk_pk_live_…", "email": "grace@example.com" }'
    ```

    `202` incondicional. Válido por uma hora. Rate limit de 5 por hora por IP.
  </Step>

  <Step title="Resgatar">
    ```bash theme={null}
    curl -s $API/v1/contact-auth/magic-link/redeem \
      -H 'Content-Type: application/json' \
      -d '{ "token": "uk_ml_…" }'
    ```

    Responde o contato e uma sessão `uk_ct_…` verificada.
  </Step>
</Steps>

O resgate é atômico em SQL, então o link funciona exatamente uma vez. Usado,
expirado e nunca existiu respondem o mesmo `401` — três verdades diferentes, uma
resposta, de propósito.

## Códigos por e-mail

O mesmo fluxo apresentado como seis dígitos. Prefira o código ao link quando ele
vai ser digitado no aparelho que o pediu: um código sobrevive ao cliente de
e-mail que abre links no navegador dele, e funciona quando a caixa de entrada está
no celular e o login está no notebook.

<Steps>
  <Step title="Pedir">
    ```bash theme={null}
    curl -s $API/v1/contact-auth/email-code \
      -H 'Content-Type: application/json' \
      -d '{ "publishable_key": "uk_pk_live_…", "email": "grace@example.com" }'
    ```

    ```json 202 — sempre theme={null}
    { "challenge_token": "uk_ec_…", "expires_at": "2026-03-01T12:10:00Z" }
    ```

    Guarde o desafio na página. Ele não é uma sessão e nunca pode ser usado como
    uma. Vale por dez minutos. Rate limit de 5 por hora por IP.
  </Step>

  <Step title="Verificar">
    ```bash theme={null}
    curl -s $API/v1/contact-auth/email-code/verify \
      -H 'Content-Type: application/json' \
      -d '{ "publishable_key": "uk_pk_live_…", "challenge_token": "uk_ec_…", "code": "418902" }'
    ```

    Responde o contato e uma sessão `uk_ct_…` verificada. O código chegou na caixa de
    entrada, então ele comprova o endereço exatamente como o link.
  </Step>
</Steps>

O desafio volta para **qualquer** endereço, tendo conta ou não — do contrário
este endpoint seria justamente onde alguém descobre quais endereços têm. A
verificação mantém a mesma linha: código errado, expirado, já usado e desafio que
nunca teve código atrás dele respondem o mesmo `401 invalid_code`.

<Note>
  Duas regras são o que torna seis dígitos seguros de aceitar, e as duas importam:

  * **Cada código tem cinco tentativas.** Um palpite gasta uma, certo ou errado, e o
    código morre quando acabam. Seis dígitos são um milhão, o que o rate limit
    sozinho não fecha.
  * **Pedir um código novo gasta o anterior.** Senão cada pedido somaria mais cinco
    palpites contra mais um número.

  O código sozinho também não basta: ele só vale contra o desafio para o qual foi
  emitido, então um código lido por cima do ombro de alguém não abre nada sem o
  navegador que o pediu.
</Note>

## A sessão

Os dois fluxos devolvem uma sessão de contato `uk_ct_…`, válida por 30 dias, com
`verified: true`. Use-a para as leituras do próprio contato:

```bash theme={null}
curl -s $API/v1/contact/me     -H "Authorization: Bearer uk_ct_…"
curl -s -X POST $API/v1/contact/logout -H "Authorization: Bearer uk_ct_…"
```

Sair apaga a sessão — a próxima busca simplesmente não encontra nada. No SDK o
par é `requestEmailCode` / `signInWithEmailCode` e `requestMagicLink` /
`redeemMagicLink` no cliente; o `/sign-in` do portal é o fluxo por código
desenhado por nós.
