Pular para o conteúdo principal

GET /v1/verify/:id

Consulta o estado atual de uma verificação, e o veredicto assim que o pipeline terminar.

GET https://api.xxuxe.online/v1/verify/vf_AG07CDWRRFQV4T05ZXG2

Autenticação

Este endpoint exige uma chave secreta. Uma chave publicável é rejeitada com 401 secret_key_required, mesmo que a mesma chave tenha tido permissão para criar e submeter a verificação.

Authorization: Bearer qv_sec_YOUR_SECRET_KEY

Em modo de teste o prefixo é qv_sect_.

O motivo não é arbitrário: uma chave publicável vai embarcada no código-fonte da sua página, onde qualquer pessoa pode lê-la. Se ela pudesse ler veredictos, o resultado de KYC de cada cliente — incluindo número do documento e data de nascimento — estaria a um fetch de distância de qualquer pessoa com as ferramentas de desenvolvedor abertas. Então os resultados são somente server-side, por construção.

Se você precisa que o navegador saiba que o fluxo terminou, o evento veridia:complete do widget informa que as imagens foram submetidas. Ele deliberadamente não carrega o veredicto. Decida no servidor.

Parâmetro de caminho

ParâmetroTipoDescrição
:idstringO verificationId do /init, por exemplo vf_AG07CDWRRFQV4T05ZXG2

status e verdict são eixos diferentes

Leia isto antes de escrever qualquer lógica de ramificação. É o erro mais caro que esta API permite.

CampoPergunta que respondeValores
statusO pipeline rodou?queued, processing, completed, failed
verdictA pessoa passou?approved, review, rejected

status: "completed" significa que a maquinaria terminou o trabalho dela. Todo solicitante rejeitado também chega a completed — é assim que uma rejeição bem-sucedida se parece.

// ERRADO — isso faz onboarding de todo solicitante que o sistema rejeitou.
// Não lança erro nenhum, não registra nada de anormal, e parece correto em
// testes enquanto todos os seus usuários de teste passarem.
if (data.status === 'completed') {
await enableAccount(userId);
}

// CERTO — os dois eixos verificados separadamente
if (data.status === 'completed') {
if (data.verdict === 'approved') await enableAccount(userId);
else if (data.verdict === 'review') await queueForManualReview(userId);
else if (data.verdict === 'rejected') await blockOnboarding(userId);
}

Uma segunda armadilha da mesma família: review é um veredicto final, não transitório. O pipeline terminou; agora uma pessoa precisa agir. Fazer polling esperando que review se resolva sozinho espera para sempre. A resolução chega como um novo evento de webhook quando um revisor decide.

Exemplo de requisição

curl

curl -X GET https://api.xxuxe.online/v1/verify/vf_AG07CDWRRFQV4T05ZXG2 \
-H "Authorization: Bearer qv_sec_YOUR_SECRET_KEY"

JavaScript / Node.js

const response = await fetch(
`https://api.xxuxe.online/v1/verify/${verificationId}`,
{ headers: { 'Authorization': `Bearer ${process.env.VERIDIA_SECRET_KEY}` } }
);
const data = await response.json();
console.log(data.status, data.verdict);

Python

import os
import requests

response = requests.get(
f"https://api.xxuxe.online/v1/verify/{verification_id}",
headers={"Authorization": f"Bearer {os.environ['VERIDIA_SECRET_KEY']}"},
)
response.raise_for_status()
data = response.json()
print(data["status"], data.get("verdict"))

Resposta

200 OK

Enquanto processa:

{
"verificationId": "vf_AG07CDWRRFQV4T05ZXG2",
"status": "processing",
"verdict": null,
"confidence": null,
"scores": null,
"flags": null,
"submittedAt": "2026-05-01T18:39:05Z",
"completedAt": null
}
As chaves estão presentes com null, não ausentes

Enquanto uma verificação está em andamento, verdict, confidence, scores, flags e completedAt são retornados como null de JSON — as chaves existem.

Então 'verdict' in data é true desde o primeiríssimo polling, e data.verdict !== undefined também é true. Nenhum dos dois é um teste válido de "já terminou?". Verifique status, ou verifique se o valor é null.

Em clientes tipados isso importa ainda mais: um campo modelado como string não opcional vai falhar na desserialização já no primeiro polling.

Quando completa:

{
"verificationId": "vf_AG07CDWRRFQV4T05ZXG2",
"status": "completed",
"verdict": "approved",
"confidence": 93.4,
"scores": {
"ocr_confidence": 78.0,
"face_match": 96.2,
"liveness": 91.5,
"doc_quality": 85.0,
"mrz_valid": 100.0,
"name_match": 97.0
},
"flags": [
{ "level": "ok", "text": "auto_approved_all_checks_passed" },
{ "level": "ok", "text": "mrz_checksums_valid" }
],
"submittedAt": "2026-05-01T18:39:05Z",
"completedAt": "2026-05-01T18:39:08Z"
}

Precisando de revisão manual:

{
"verificationId": "vf_AG07CDWRRFQV4T05ZXG2",
"status": "completed",
"verdict": "review",
"confidence": 64.5,
"scores": {
"ocr_confidence": 88.0,
"face_match": 71.2,
"liveness": null,
"doc_quality": 55.0,
"mrz_valid": 0.0,
"name_match": 62.0
},
"flags": [
{ "level": "warn", "text": "mrz_checksum_failed" },
{ "level": "warn", "text": "heavy_glare" }
],
"submittedAt": "2026-05-01T18:39:05Z",
"completedAt": "2026-05-01T18:39:08Z"
}

Campos da resposta

CampoTipoPresenteDescrição
verificationIdstringSempreO ID da verificação
statusstringSemprequeued, processing, completed, failed
verdictstring | nullnull até completedapproved, review, rejected
confidencenumber | nullnull até completedScore ponderado geral, 0-100
scoresobject | nullnull até completedDetalhamento por sinal — veja abaixo
flagsarray | nullnull até completedObjetos { level, text }
submittedAtstringSempreISO 8601 UTC, quando o /submit foi chamado
completedAtstring | nullnull até o estado terminalISO 8601 UTC

scores — as chaves são snake_case

Seis chaves, todas em snake_case. Este é o campo que integradores mais erram, porque o erro é silencioso.

ChaveFaixaSignificado
ocr_confidence0-100A confiança autodeclarada do modelo de extração no texto do documento
face_match0-100Similaridade biométrica entre a selfie e a foto do documento
liveness0-100 ou nullSinal anti-spoofing
doc_quality0-100Qualidade da imagem do documento (nitidez, reflexo, resolução, moiré)
mrz_valid0-100Validade do checksum da zona de leitura mecânica (MRZ)
name_match0-100Correspondência aproximada entre submittedFullName e o nome lido por OCR

Duas coisas com que tomar cuidado:

Não existe faceMatch. Em JavaScript, scores.faceMatch é undefined, e undefined < 70 avalia para false. Então um limiar escrito em camelCase não lança erro — ele silenciosamente nunca dispara, e todo solicitante passa pela sua checagem. Se você está portando um limiar de uma versão antiga desta documentação, esta é a linha a corrigir.

liveness pode ser null. Ele é null quando nenhum sinal de prova de vida foi produzido ou quando o modelo de prova de vida deu erro. É o único membro anulável de scores. Proteja-o antes de fazer aritmética:

const liveness = data.scores.liveness;
if (liveness !== null && liveness < 50) {
// ...
}

mrz_valid e name_match são os dois sinais de fraude documental mais frequentemente ignorados. name_match, em particular, é o score produzido a partir do submittedFullName que você enviou ao /init — se você envia esse campo, leia este score.

flags

flags é um array de objetos, não de strings:

{ "level": "warn", "text": "heavy_glare" }

Níveis

São exatamente três, e um deles significa bom:

NívelSignificado
okUma checagem passou. Este é um sinal positivo, não um problema
warnAlgo está fora do esperado, mas não é desqualificante
errUm sinal sério — falha grave, correspondência em lista de sanções, ou indicador de falsificação

Não existe info nem critical. Duas consequências que vale dizer:

  • flags.some(f => f.level === 'critical') é sempre false. Sinais sérios são err. Uma regra de prioridade para revisores escrita contra critical nunca dispara, e os casos que mais precisam de um humano recebem prioridade normal.
  • Um array flags não vazio não significa que algo está errado. Toda verificação aprovada carrega ao menos { "level": "ok", "text": "auto_approved_all_checks_passed" }. Tratar flags.length > 0 como "problema" manda 100% das suas aprovações limpas para revisão manual.

Filtre pelo nível:

const problems = data.flags.filter(f => f.level !== 'ok');
const serious = data.flags.filter(f => f.level === 'err');

Flags que você realmente vai ver

textNível típicoSignificado
auto_approved_all_checks_passedokAprovado sem falhas graves. Sempre o primeiro quando presente
image_blurrywarnNitidez abaixo do limiar
heavy_glarewarnReflexo forte obscurecendo o documento
possible_screen_capturewarnPadrão de moiré — o "documento" pode ser uma foto de uma tela
low_resolutionwarnResolução da imagem baixa demais
mrz_checksums_validokChecksums da MRZ verificados
mrz_checksum_failedwarn / errMRZ presente mas os checksums não conferem
mrz_viz_consistentokA MRZ concorda com os campos impressos
mrz_viz_mismatcherrMRZ válida que contradiz os campos impressos — forte sinal de falsificação
no_face_detected_on_documenterrNenhum rosto encontrado na foto do documento
no_face_detected_on_selfieerrNenhum rosto encontrado na selfie
face_match_below_critical_thresholderrÉ muito improvável que a selfie e a foto do documento sejam a mesma pessoa
document_quality_unusableerrImagem do documento degradada demais para ser avaliada
active_liveness_spooferrO desafio de prova de vida ativa concluiu que a captura não era ao vivo
active_liveness_liveokO desafio passou
aml_sanctions_matcherrCorrespondência forte com uma lista oficial de sanções
aml_possible_matchwarnCorrespondência de sanções mais fraca, vale olhar
missing_imageserrAs imagens esperadas não estavam presentes no momento do pipeline

As três que carregam peso regulatório ou de fraude e são as mais fáceis de deixar passar: possible_screen_capture (injeção de imagem), mrz_viz_mismatch (documento fabricado) e aml_sanctions_match (a pessoa está em uma lista de sanções). Encaminhe essas para algum lugar onde uma pessoa as veja.

Não exiba o texto das flags para o seu usuário final. Dizer a alguém em qual checagem ele falhou diz a um atacante exatamente o que corrigir.

Como o veredicto é decidido

VeredictoCondição
approvedconfidence >= 90, e nenhuma falha grave, e nenhuma retenção de compliance
reviewQualquer coisa entre os dois — incluindo todo caso com falha grave que não seja catastrófica
rejectedconfidence < 60

Três regras que os números sozinhos não contam:

  1. Uma falha grave nunca aprova automaticamente, qualquer que seja o score. Qualquer falha grave de nível err limita o resultado a review, ou a rejected se a confiança também estiver abaixo de 60.
  2. Uma correspondência forte de sanções força review. Ela rebaixa um caso que de outra forma seria aprovado; ela nunca rejeita automaticamente. Um acerto de sanções é uma decisão de compliance para uma pessoa, não para uma máquina.
  3. A faixa 60-89 é review, não approved. Se você está acostumado com um limiar de 80, é essa a lacuna que vai encher a sua fila manual inesperadamente.

Os limiares são configurações no nível do deployment, não configuração por tenant — não existe hoje um botão por cliente para eles.

Se você reimplementar o limiar do seu lado lendo confidence, você vai perder as regras 1 e 2 e vai aprovar automaticamente casos que o sistema deliberadamente segurou, incluindo correspondências de sanções. Leia verdict.

Máquina de estados do status

queued -> processing -> completed
-> failed
StatusSignificado
queuedAguardando na fila do backend
processingPipeline em execução
completedPipeline terminou. Agora leia verdict
failedO pipeline não conseguiu concluir. Não há veredicto — não leia um

failed não é uma rejeição. Significa que o sistema não conseguiu chegar a uma conclusão. Trate como um caso de tentar de novo ou escalar, não como uma decisão sobre o solicitante.

Polling

Webhooks são melhores: eles disparam assim que o veredicto está pronto, e também entregam o evento posterior em que uma pessoa resolve um review. O polling não consegue ver esse segundo resultado a menos que você faça polling indefinidamente. Veja Webhooks.

Se você precisa mesmo fazer polling:

async function waitForVerdict(verificationId, timeoutMs = 30000) {
const start = Date.now();
let interval = 1000; // 1s — veja a nota sobre limite de taxa abaixo

while (Date.now() - start < timeoutMs) {
const response = await fetch(
`https://api.xxuxe.online/v1/verify/${verificationId}`,
{ headers: { 'Authorization': `Bearer ${process.env.VERIDIA_SECRET_KEY}` } }
);
const data = await response.json();

if (data.status === 'completed' || data.status === 'failed') {
return data; // quem chamou ainda precisa ramificar por data.verdict
}

await new Promise(r => setTimeout(r, interval));
interval = Math.min(interval * 1.5, 3000);
}

throw new Error('Verification timed out');
}

Repare que o loop retorna em qualquer um dos status terminais. Quem chama precisa tratar failed, onde verdict é null.

Não faça polling mais rápido que 1 Hz

O limite por tenant aqui é 600/minuto, mas o limite por IP é 100/minuto e ele é verificado primeiro. Fazer polling de um servidor a cada 500 ms são 120 requisições por minuto e será limitado muito antes do limite do tenant. Veja Limites de taxa.

Erros

HTTPCódigo de erroQuando
401missing_api_keySem cabeçalho Authorization
401invalid_api_keyChave revogada, malformada, ou que nunca existiu
401secret_key_requiredUma chave publicável foi usada. O erro mais comum neste endpoint
404verification_not_foundO ID não existe, está malformado, ou pertence a outro tenant
429rate_limitedLimite por IP ou por tenant
500internal_errorReporte o requestId
503backend_unavailableBackend inacessível — transitório, tente de novo com backoff

Se você chega aqui vindo de /init e /submit reutilizando a mesma chave e recebe um 401, confira o código antes de mexer na chave. secret_key_required significa que a chave é perfeitamente válida — ela é apenas da família errada para esta chamada. Rotacioná-la não vai ajudar.

Uma verificação que pertence a outro tenant retorna 404, não 403: quem não é dono de uma verificação não deve descobrir que ela existe.

Catálogo completo: Erros.

Notas

  • Verificações não se tornam ilegíveis com o tempo. A posse é verificada contra o tenant registrado com a própria verificação, então este endpoint continua funcionando muito depois de a intenção de upload de uma hora ter expirado. Essa expiração se aplica aos uploads e ao /submit, não à leitura dos resultados.
  • Uma vez completed ou failed, a resposta de uma dada execução automatizada é estável — mas um caso em review pode mudar depois, quando uma pessoa o resolver. Se você faz cache, invalide no webhook.
  • submittedAt e completedAt são ISO 8601 em UTC. (expiresAt no /init é diferente — aquele é em segundos Unix.)
  • userRef não é retornado aqui. Ele viaja apenas em webhooks. Mantenha o seu próprio mapeamento verificationId → usuário.

O que vem a seguir

  • Webhooks — receba veredictos por push, incluindo resultados de revisão humana
  • Erros — referência completa de códigos de erro
  • Limites de taxa — por que o polling bate em um limite antes do que você esperaria