Fluxo de OCR + Biometria
Um onboarding por documento responde a quatro perguntas, sempre nesta ordem:
- Que documento é este?
- O que está escrito nele — e bate com a Receita Federal?
- A pessoa da selfie é a mesma do documento?
- Esse CPF corresponde a uma pessoa registrada?
Cada pergunta tem um endpoint, e a referência descreve todos eles em detalhe. O que ela não descreve é a ordem, o que fazer quando um passo não devolve o esperado e como o resultado de um passo alimenta o seguinte — que é o assunto desta página.
A primeira pergunta é a única opcional: o Full OCR já classifica o documento por dentro. Quando vale chamar o classificador antes é a primeira decisão do fluxo.
Visão geral
| Passo | Endpoint | Pergunta que ele responde | O que você leva adiante |
|---|---|---|---|
| 1 (opcional) | POST /classify/v3 | Que documento é este, e qual lado eu recebi? | a decisão de seguir, pedir o outro lado, ou rotear |
| 2 | POST /full-ocr/v4 | O que está escrito, e confere com a Receita? | enhanced.person.taxId e a imagem do documento |
| 3 | POST /face-match/v2 | A selfie é da mesma pessoa do documento? | matched e confidence |
| 4 | GET /bureau/v2/natural-person/{CPF} | Esse CPF corresponde a uma pessoa registrada? | nome, nome da mãe e data de nascimento oficiais |
arquivos do documento
(frente + verso)
│
│ opcional — só quando há uma decisão a tomar antes de extrair
├──▶ POST /classify/v3
│ → data[0].classification.type / .subtype / .sides[].side
│
▼
POST /full-ocr/v4?returnsFaceInfo=true
→ data[0].enhanced.person ........ os dados lidos, já normalizados
→ data[0].taxData e .matches ..... cruzamento com a Receita Federal
→ data[0].classification ......... que documento era, afinal
│
│ + a selfie do usuário
▼
POST /face-match/v2
→ data[0].matched e data[0].confidence
│
│ com o enhanced.person.taxId
▼
GET /bureau/v2/natural-person/{taxId}
→ data.name, data.mothersName, data.birthdate‼️ Informe a versão na URL do Full OCR
POST /full-ocr, sem versão no caminho, é atendido pela v2 — não pela v4. A v2 tem outro formato de resposta, não separa extraction de enhanced e ignora todos os parâmetros de query. Chame sempre /full-ocr/v4.
Passo 1 — classificar, e quando pular
O Classificador responde que documento é este e nada mais: tipo, subtipo, país e lado, com a confiança da classificação. Ele não lê o conteúdo do documento.
O Full OCR faz a mesma classificação por dentro — ela volta em data[].classification de toda resposta — e extrai os campos e cruza com a Receita Federal, tudo em uma chamada. Por isso, a maioria dos fluxos não precisa do passo 1.
A pergunta que decide é simples: existe alguma decisão a tomar antes de extrair?
Chame o /classify/v3 antes quando… | Vá direto ao /full-ocr/v4 quando… |
|---|---|
| Você precisa validar a captura enquanto o usuário ainda está na tela — descobrir que só chegou o verso e pedir a frente antes de gastar a extração. | Você já sabe que vai extrair de qualquer forma. A classificação vem junto, sem custo adicional. |
| Você precisa rotear por tipo: comprovante de residência tem endpoint dedicado, e outros tipos podem simplesmente não servir ao fluxo. | O seu fluxo aceita um conjunto fechado de documentos e trata tudo o que não for reconhecido do mesmo jeito. |
| Você quer recusar cedo um documento fora da lista aceita, sem pagar pela análise completa. | Você recebe frente e verso juntos e confia na captura. |
🚧 O passo 1 é uma chamada a mais, e ela é tarifada
Toda resposta 2xx gera cobrança, inclusive uma classificação que não encontrou documento nenhum — veja Tarifação. Classificar "por via das dúvidas", antes de um /full-ocr que aconteceria de qualquer jeito, dobra o número de chamadas para obter a mesma informação.
Some-se a isso que o /classify/v3 aceita um arquivo por chamada, enquanto o /full-ocr aceita até sete: classificar frente e verso são duas chamadas, e depois ainda vem a do OCR.
A saída do classificador é uma lista ordenada do melhor para o pior resultado. Uma imagem sem documento reconhecível devolve 200 com "data": [] — não é erro, é resposta. Leia data[0] só depois de confirmar que a lista não está vazia.
Passo 2 — extrair os dados e cruzar com a Receita
O Full OCR devolve, para cada documento reconhecido, três blocos que interessam ao fluxo:
extraction— o texto como saiu do OCR, cru.enhanced— o mesmo dado normalizado e corrigido. É este que você persiste e é dele que sai o CPF do passo 4, emenhanced.person.taxId, sem máscara.taxDataematches— o que a Receita Federal devolveu e o resultado campo a campo da comparação com o que estava escrito no documento.
curl -i -X POST 'https://api-homolog.nxcd.app/full-ocr/v4?returnsFaceInfo=true' \
--header 'Authorization: ApiKey SUA_CHAVE_AQUI' \
--form 'frente=@./cnh-frente.jpg' \
--form 'verso=@./cnh-verso.jpg'Envie o documento inteiro — o cruzamento depende do lado
O CPF não está impresso nos dois lados de todo documento, então a consulta à Receita Federal depende da face enviada. Uma CNH só com o verso, por exemplo, não dispara a consulta: taxData e matches continuam na resposta, mas vazios e com todos os matches em false.
Esse é o principal motivo prático para validar a captura antes — seja no passo 1, seja na sua própria interface. As regras por tipo de documento estão em taxData e matches.
Duas leituras que costumam ser feitas errado:
dataé uma lista, ordenada do melhor para o pior resultado. Se você espera um único documento, leiadata[0]— mas confirme antes que a lista não veio vazia. Arquivo ilegível devolve200com"data": [].matches.name: falsenem sempre significa "nome diferente". Um nome lido com menos de 8 caracteres — sintoma típico de OCR que falhou — é tratado como não conferido, e o cruzamento para ali. Nesse caso, o caminho é melhorar a captura, não reprovar a pessoa.
Precisa também da biometria oficial?
O Full OCR + Datavalid soma a validação no Datavalid da SERPRO à mesma chamada. Ele substitui este passo; não se acumula com ele.
Passo 3 — comparar a face do documento com a selfie
Até aqui, o fluxo provou que o documento existe e que os dados dele conferem com a Receita. Falta provar que quem está enviando é a pessoa do documento.
O Face Match recebe duas imagens na mesma requisição — a do documento e a selfie — e responde se as faces são da mesma pessoa:
curl -i -X POST 'https://api-homolog.nxcd.app/face-match/v2' \
--header 'Authorization: ApiKey SUA_CHAVE_AQUI' \
--form 'documento=@./cnh-frente.jpg' \
--form 'selfie=@./selfie.jpg'Os nomes dos campos são livres e voltam na resposta, dentro de data[].resources[].fieldname — é por eles que você identifica qual imagem é qual.
🚧 "data": [] não é erro, e data[0] pode não existir
A comparação só acontece quando as duas imagens têm alguma face detectável. Faltando uma face, a resposta é 200 com a lista vazia, e um código que lê data[0].matched direto quebra em vez de receber um resultado negativo. Confira o tamanho de data antes.
Três decisões deste passo:
- A selfie veio de uma sessão de liveness? Então o endpoint é outro: o Face Match for Liveness reaproveita a selfie já capturada e você envia só o documento. O encadeamento completo está no Fluxo de Liveness — e é ele que acrescenta o liveness, que o
/face-matchsozinho não faz: comparar faces não distingue uma pessoa presente de uma foto de foto. - Qual imagem do documento enviar? A original serve. Se você quiser exibir ou auditar o recorte da face que estava no documento, o
/full-ocr/v4comreturnsFaceInfo=truejá devolveu esse recorte emdata[].face.croppedBase64— mas a comparação em si é sempre uma chamada ao/face-match. - Precisa da biometria do governo? O Face Match + Datavalid confronta a face com a base oficial a partir de um CPF.
Um parâmetro de query errado se comporta de forma diferente em cada endpoint
No /face-match, um parâmetro desconhecido derruba a requisição com 422. No /full-ocr, o mesmo erro é ignorado em silêncio: a resposta vem 200, sem o campo que você esperava. Um returnFaceInfo escrito sem o s não avisa nada — só devolve uma resposta sem a face.
Passo 4 — cruzar com o bureau
O Bureau PF consulta os dados da Receita Federal a partir do CPF:
GET /bureau/v2/natural-person/12345678909O CPF sai de data[0].enhanced.person.taxId do passo 2, já sem máscara — embora o endpoint aceite as duas formas, com ou sem.
Nem todo fluxo precisa deste passo. Quando o /full-ocr/v4 conseguiu cruzar, taxData já traz nome, nome da mãe e data de nascimento oficiais, e matches já diz o que bateu. O Bureau PF é o caminho direto quando:
- o documento não carrega CPF, ou o cruzamento não aconteceu porque faltou o lado certo — os blocos vieram vazios;
- o CPF que você quer verificar foi digitado pelo usuário, e não lido do documento, e você precisa confrontá-lo com o que o documento disse;
- você precisa dos dados oficiais em um momento do fluxo em que não há documento nenhum em mãos.
🚧 CPF não encontrado devolve 200 com "data": {}
Não é 404. Teste se o objeto veio vazio antes de ler data.name. E a chamada é tarifada do mesmo jeito, porque houve processamento.
Cuidado ao comparar datas entre os dois endpoints
taxData.birthdate, no Full OCR, é uma string no formato "1990-02-01". data.birthdate, no Bureau PF, é um número no formato 19990201. Normalize antes de comparar — a comparação direta entre os dois nunca dá igual.
Quando um passo não devolve o esperado
| Sintoma | Causa provável | O que fazer |
|---|---|---|
Resposta em formato inesperado, sem enhanced | POST /full-ocr chamado sem versão: a chamada foi atendida pela v2. | Usar /full-ocr/v4. Confira o campo version da resposta. |
| Parâmetro de query sem efeito no Full OCR | Nome escrito errado, ou parâmetro que só vale na v4 usado em outra versão. Nenhum dos dois gera erro. | Conferir a grafia e a versão. Booleanos só ligam com a string exata true. |
"data": [] no classificador ou no Full OCR | Nenhum documento reconhecido — imagem borrada, cortada, escura, ou tipo fora da lista aceita. | Não é erro nem falha: é 200 tarifado. Peça nova captura. |
taxData e matches vazios, todos os matches em false | O lado enviado não permite a consulta à Receita, ou o documento não tem CPF a cruzar (CRLV, ANTT). | Reenviar com frente e verso. As regras por tipo estão na referência. |
matches.name: false com o nome visivelmente certo | Nome lido com menos de 8 caracteres, tratado como não conferido; ou OCR com ruído. | Comparar com o extraction para ver o que foi lido de fato, e melhorar a captura. |
"data": [] no Face Match | Uma das imagens não tem face detectável — documento cortado, selfie escura, PDF cuja primeira página com face não é a esperada. | Verificar o tamanho de data antes de ler data[0], e reenviar as imagens. |
422 no Face Match | Parâmetro de query desconhecido ou com valor inválido, formato de arquivo recusado, ou mais de dois arquivos. | O /face-match recebe exatamente duas imagens. |
422 no Full OCR logo na entrada | federalRevenueNumber que não passa na validação do dígito verificador, ou mais de sete arquivos. | O CPF é validado antes de qualquer processamento e derruba a requisição inteira. |
"data": {} no Bureau PF | CPF não localizado. | Tratar como "não encontrado", não como erro. A resposta é 200. |
A lista completa de códigos e o formato das respostas de erro estão em Códigos HTTP das respostas. As duas formas de enviar arquivos — multipart e base64 — e os limites de tamanho estão em Envio de arquivos.
O que encadeia com o quê
| Valor | Nasce em | É usado em |
|---|---|---|
classification.type / .sides[].side | POST /classify/v3 | a sua decisão de rotear, pedir o outro lado ou seguir |
enhanced.person.taxId | POST /full-ocr/v4 | caminho do GET /bureau/v2/natural-person/{CPF} |
| a imagem do documento | a captura do usuário | POST /full-ocr/v4 e POST /face-match/v2 |
Nextid-ReqId | header de todas as respostas | seus logs — é por ele que o suporte localiza uma chamada |
Próximos passos
- Classificador — a lista completa de tipos, subtipos e lados reconhecidos
- Full OCR — todos os blocos da resposta, os parâmetros de query e as diferenças entre v2, v3 e v4
- Face Match — a resposta completa, os parâmetros de query e os erros
- Bureau PF — os campos devolvidos pela consulta
- Fluxo de Liveness — como somar o liveness a este fluxo