POST /v1/verify/submit
Depois que o cliente enviou o documento e a selfie, chame /submit para executar o pipeline de verificação.
POST https://api.xxuxe.online/v1/verify/submit
Esta chamada:
- Confirma que as chaves submetidas são as que o
/initemitiu para esta verificação - Confirma que as imagens de fato chegaram ao armazenamento
- Executa OCR no documento via Workers AI
- Despacha o job para o backend de ML para comparação facial, prova de vida e veredicto
- Retorna
202 Accepted
Tudo depois do passo 5 é assíncrono.
Autenticação
Bearer token. Qualquer chave pertencente ao mesmo tenant que a usada no /init.
Authorization: Bearer qv_pub_FJJWXMA2RN2XPRDK6YJX4KTVD0XSQHW9
A checagem é sobre o tenant, não sobre a chave. Então um padrão legítimo e muitas vezes preferível funciona: inicie a verificação no navegador com a sua chave publicável e depois submeta a partir do seu próprio servidor com a sua chave secreta. Submissão entre tenants diferentes é rejeitada.
Corpo da requisição
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
verificationId | string | Sim | Do /init. Precisa casar com ^vf_[A-Za-z0-9]{16,24}$ |
keys.docFront | string | Sim | A key de init.uploads.docFront. Máx. 512 caracteres |
keys.selfie | string | Sim | A key de init.uploads.selfie. Máx. 512 caracteres |
keys.docBack | string | Não | A key de init.uploads.docBack, se você a capturou |
livenessScore | number | Não | Score de prova de vida calculado no cliente, 0-100. Usado como sinal suave adicional |
metadata | object | Não | Aceito pelo schema. Veja o aviso abaixo |
keys é obrigatório, e não é decorativo
keys é o que amarra os bytes armazenados a esta verificação. Não há como submeter sem ele.
Os valores precisam corresponder exatamente ao que o /init retornou. Pegue-os da resposta do init em vez de reconstruir as strings — uma divergência é rejeitada com doc_front_key_mismatch, selfie_key_mismatch ou doc_back_key_mismatch.
metadata é aceito e depois descartadoO schema valida metadata, e a requisição retorna 202. Mas o campo não é encaminhado ao backend e não é devolvido em webhooks. Ele para na borda do Worker.
Este é o pior tipo de falha — pega os seus dados, tem sucesso, e os joga fora. Se você planejava rotear por um ID de campanha, um bucket de teste A/B ou uma tag de marca, isso não vai funcionar.
Use userRef no /init no lugar. Ele é persistido, e é o único campo devolvido nos payloads de webhook.
Exemplo de requisição
curl
curl -X POST https://api.xxuxe.online/v1/verify/submit \
-H "Authorization: Bearer qv_pub_FJJWXMA2RN2XPRDK6YJX4KTVD0XSQHW9" \
-H "Content-Type: application/json" \
-d '{
"verificationId": "vf_AG07CDWRRFQV4T05ZXG2",
"keys": {
"docFront": "verif/tn_xyz/vf_AG07CDWRRFQV4T05ZXG2/doc-front.jpg",
"selfie": "verif/tn_xyz/vf_AG07CDWRRFQV4T05ZXG2/selfie.jpg"
},
"livenessScore": 92.5
}'
JavaScript / Node.js
const response = await fetch('https://api.xxuxe.online/v1/verify/submit', {
method: 'POST',
headers: {
'Authorization': `Bearer ${publishableKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
verificationId: init.verificationId,
keys: {
docFront: init.uploads.docFront.key,
selfie: init.uploads.selfie.key,
// docBack: init.uploads.docBack.key, // apenas se você o capturou
},
livenessScore: 92.5,
}),
});
const data = await response.json(); // 202 Accepted
console.log('Submitted. Poll:', data.statusUrl);
Python
import os
import requests
response = requests.post(
"https://api.xxuxe.online/v1/verify/submit",
headers={
"Authorization": f"Bearer {os.environ['VERIDIA_PUBLISHABLE_KEY']}",
"Content-Type": "application/json",
},
json={
"verificationId": init["verificationId"],
"keys": {
"docFront": init["uploads"]["docFront"]["key"],
"selfie": init["uploads"]["selfie"]["key"],
},
"livenessScore": 92.5,
},
)
response.raise_for_status()
print("Poll:", response.json()["statusUrl"])
Resposta
202 Accepted
{
"verificationId": "vf_AG07CDWRRFQV4T05ZXG2",
"status": "queued",
"statusUrl": "https://api.xxuxe.online/v1/verify/vf_AG07CDWRRFQV4T05ZXG2"
}
| Campo | Tipo | Descrição |
|---|---|---|
verificationId | string | O mesmo ID que você submeteu |
status | string | queued, processing ou completed |
statusUrl | string | Onde fazer polling do veredicto |
status aqui não é um veredictostatus: "completed" nesta resposta — ou em qualquer polling posterior — significa que o pipeline terminou. Não significa que a pessoa passou. O resultado vive em um campo separado, verdict, e ele não está nesta resposta de forma alguma.
Ler o veredicto exige uma chave secreta em GET /v1/verify/:id. A chave publicável que fez esta chamada não consegue obtê-lo.
Idempotência
Chamar /submit duas vezes com o mesmo verificationId é seguro.
O backend deduplica: se a verificação já está processing ou completed, a duplicata é ignorada e você recebe de volta o status atual em vez de uma segunda execução do pipeline. Uma verificação ainda em queued ou failed pode ser reenfileirada.
O resultado do OCR fica em cache por 30 minutos, então uma nova tentativa também não bate de novo no Workers AI.
Então, se o seu cliente receber um erro de rede depois de enviar /submit, tente de novo com o mesmo corpo. Você não vai criar uma verificação duplicada.
A autenticação rejeita requisições quando o saldo de créditos do tenant está zerado (insufficient_credits, 402), mas este endpoint atualmente não decrementa esse saldo a cada verificação. Não construa previsão de uso nem um medidor de consumo assumindo que um submit equivale a um crédito a menos no contador — reconcilie contra os seus próprios registros.
Tempos
O 202 tipicamente volta em alguns segundos; a chamada de OCR domina. O veredicto em si chega alguns segundos depois disso, de forma assíncrona.
Em vez de fazer polling por ele, use webhooks. Se você precisa mesmo fazer polling, GET /v1/verify/:id tem o padrão — e note que fazer polling de um único servidor bate no limite de taxa por IP muito antes do limite por tenant.
Erros
| HTTP | Código de erro | detail.reason | Quando |
|---|---|---|---|
400 | invalid_body | — | O corpo falhou na validação. Veja detail.fieldErrors |
400 | invalid_body | doc_front_key_mismatch | keys.docFront não é o que o /init retornou |
400 | invalid_body | selfie_key_mismatch | keys.selfie não é o que o /init retornou |
400 | invalid_body | doc_back_key_mismatch | keys.docBack não é o que o /init retornou |
400 | invalid_body | doc_front_not_uploaded | Nenhum objeto naquela chave — o cliente nunca enviou |
400 | invalid_body | selfie_not_uploaded | O mesmo, para a selfie |
400 | invalid_body | doc_front_disappeared | O objeto existia no momento da checagem mas sumiu antes do OCR (muito raro) |
401 | missing_api_key / invalid_api_key | — | Veja Autenticação |
402 | insufficient_credits | — | Saldo do tenant está zerado |
404 | verification_not_found | — | Nunca criada via /init, expirada após 1 hora, ou pertence a outro tenant |
429 | rate_limited | — | 30/min por tenant, ou o limite por IP |
500 | internal_error | — | Reporte o requestId |
503 | backend_unavailable | — | Pipeline inacessível. Tente de novo com backoff |
insufficient_credits é 402, não 403. O código de erro interno é internal_error, não internal. Catálogo completo: Erros.
Se você recebe doc_front_not_uploaded acreditando que enviou a imagem, a causa usual é um upload que falhou com missing_upload_token porque o cliente remontou os cabeçalhos em vez de encaminhar slot.headers literalmente. Veja Enviando as imagens.
Notas
- A verificação precisa ter sido criada na última hora. A intenção expira depois disso e você recebe
verification_not_found. livenessScoreé opcional. É um sinal suave que empurra a confiança ponderada; ele nunca decide o resultado sozinho.- A proveniência da captura é lida a partir de metadados carimbados pelo servidor quando os bytes chegaram, não a partir do corpo desta requisição. O
/submitnão pode reescrevê-la.
O que vem a seguir
GET /v1/verify/:id → — obtenha o veredicto