POST /v1/verify/submit
Apos seu cliente ter feito upload das imagens do documento e da selfie ao R2 (usando as URLs presigned do /init), chame /submit para disparar o pipeline de verificacao.
POST https://api.xxuxe.online/v1/verify/submit
Esta e a chamada que:
- Verifica que todas as imagens subidas chegaram realmente ao R2
- Roda OCR sobre o documento via Workers AI
- Debita um credito do balance do tenant
- Despacha o job ao backend ML para face match + liveness + veredicto
- Retorna
202 Acceptedcom uma status URL para fazer polling
Autenticacao
Bearer token. A mesma key usada em /init (o tenant deve coincidir).
Authorization: Bearer qv_pub_FJJWXMA2RN2XPRDK6YJX4KTVD0XSQHW9
Request body
| Campo | Tipo | Obrigatorio | Descricao |
|---|---|---|---|
verificationId | string | Sim | O ID retornado por /init. Formato: vf_[A-Za-z0-9]{16,24} |
keys.docFront | string | Sim | A R2 key retornada por /init para a frente do documento. Max 512 chars |
keys.selfie | string | Sim | A R2 key retornada por /init para a selfie. Max 512 chars |
keys.docBack | string | Nao | A R2 key para o verso, se seu fluxo capturou |
livenessScore | number | Nao | Score de liveness opcional do MediaPipe (0-100). Usado como sinal adicional |
metadata | object | Nao | Pares key-value opcionais para seu bookkeeping. Ecoado em webhooks |
Os valores de keys.* devem coincidir exatamente com o que /init retornou. Submeter keys arbitrarias e rejeitado (doc_front_key_mismatch, selfie_key_mismatch, doc_back_key_mismatch).
Request de exemplo
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,
"metadata": {
"campaign": "spring_2026",
"platform": "web"
}
}'
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: initResponse.verificationId,
keys: {
docFront: initResponse.uploads.docFront.key,
selfie: initResponse.uploads.selfie.key,
},
livenessScore: 92.5,
}),
});
if (response.status === 202) {
const data = await response.json();
console.log('Submetido, polling em:', 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_response["verificationId"],
"keys": {
"docFront": init_response["uploads"]["docFront"]["key"],
"selfie": init_response["uploads"]["selfie"]["key"],
},
"livenessScore": 92.5,
},
)
response.raise_for_status()
data = response.json()
print("Status URL:", data["statusUrl"])
PHP
<?php
$payload = json_encode([
"verificationId" => $initResponse['verificationId'],
"keys" => [
"docFront" => $initResponse['uploads']['docFront']['key'],
"selfie" => $initResponse['uploads']['selfie']['key'],
],
"livenessScore" => 92.5,
]);
$ch = curl_init("https://api.xxuxe.online/v1/verify/submit");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . $_ENV['VERIDIA_PUBLISHABLE_KEY'],
"Content-Type: application/json",
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "Status URL: " . $data['statusUrl'];
Resposta
202 Accepted
{
"verificationId": "vf_AG07CDWRRFQV4T05ZXG2",
"status": "queued",
"statusUrl": "https://api.xxuxe.online/v1/verify/vf_AG07CDWRRFQV4T05ZXG2"
}
Campos de resposta
| Campo | Tipo | Descricao |
|---|---|---|
verificationId | string | O mesmo ID que voce enviou — confirma que a request foi aceita |
status | string | Estado atual: queued, processing, ou completed |
statusUrl | string | Onde fazer polling para o veredicto (tipicamente /v1/verify/:id) |
A verificacao agora esta no pipeline. O veredicto tipicamente volta em 2-3 segundos. Use a statusUrl para obte-lo.
Idempotencia
Chamar /submit duas vezes com o mesmo verificationId e seguro. O backend deduplica por verificationId, entao voce obtem o mesmo resultado em ambas as vezes. O resultado do OCR e cacheado por 30 minutos — retries nao re-disparam chamadas ao Workers AI.
Isso significa: se seu cliente receber um erro de rede apos enviar /submit, tente de novo com o mesmo body. Voce nao vai dobrar o faturamento nem criar verificacoes duplicadas.
Pipeline nos bastidores
Quando /submit e chamado, isto e o que acontece (a API retorna imediatamente no passo 5 — o resto e async):
| Passo | Latencia | O que |
|---|---|---|
| 1 | <50ms | Valida o body da request (Zod schema) |
| 2 | <100ms | Busca o intent do cache KV |
| 3 | <200ms | Verifica que os objetos R2 existem via chamadas HEAD |
| 4 | <2s | Roda OCR via Workers AI (cacheado em retries) |
| 5 | <200ms | Despacha ao backend ML, retorna 202 |
| 6 | async | Backend roda face match + liveness + veredicto |
| 7 | async | Backend persiste no MySQL + dispara webhook |
Tempo total de /submit ao veredicto: tipicamente 2-3 segundos.
Erros
| HTTP | Error code | Quando |
|---|---|---|
400 | invalid_body | O body falhou validacao. Veja detail.fieldErrors |
400 | invalid_body (com reason: "doc_front_key_mismatch") | O keys.docFront nao coincide com o que /init retornou |
400 | invalid_body (com reason: "selfie_key_mismatch") | O keys.selfie nao coincide com o que /init retornou |
400 | invalid_body (com reason: "doc_front_not_uploaded") | O objeto R2 nao existe — o cliente nunca subiu |
400 | invalid_body (com reason: "selfie_not_uploaded") | Mesmo, para a selfie |
404 | verification_not_found | O verificationId nunca foi criado via /init, ou o intent expirou (>1h) |
401 | unauthorized / invalid_key | Issue de auth. Veja Autenticacao |
403 | insufficient_credits | O tenant tem 0 creditos |
429 | rate_limited | Atingiu o rate limit de /submit (30 req/min por padrao) |
500 | internal | Algo quebrou. Inclua o requestId no seu ticket de suporte |
Catalogo completo: Erros.
Notas
- O
verificationIddeve ter sido criado na ultima hora. Apos isso, o intent expira e voce obtemverification_not_found - A mesma API key usada em
/initdeve ser usada aqui. Submission cross-tenant e rejeitada - O
livenessScoree opcional mas recomendado — mesmo um score basico browser-side melhora o accuracy do veredicto metadatae opaco para Veridia — apenas e ecoado em webhooks. Use para coisas como buckets de A/B test, campaign IDs, ou tags de plataforma
Proximos passos
Agora que a verificacao esta no pipeline, obtenha o veredicto: