Face Match
Este endpoint recebe duas imagens, localiza uma face em cada uma e responde se as duas faces são da mesma pessoa.
O uso mais comum é confrontar uma selfie com a foto impressa em um documento de identificação, mas a comparação não exige isso: qualquer par de imagens com uma face detectável serve — duas selfies, dois documentos, ou um de cada.
🚧 Face Match não é Face
- Face Match (esta página) — recebe duas imagens e responde se são a mesma pessoa.
- Face — recebe uma imagem e apenas descreve as faces que existem nela, sem comparar nada.
Este endpoint não faz liveness — ele não distingue uma pessoa presente na captura de uma foto de foto. Para isso, use o Liveness. E ele não consulta bases oficiais: se você precisa confrontar a face com a biometria do governo para um CPF, use o Face Match + Datavalid.
TIP
Se a selfie vem de uma sessão de liveness feita com os nossos SDKs, prefira o Face Match for Liveness: ele reaproveita a selfie já capturada, e você envia apenas o documento.
Request
POST/face-match/v2Headers
Authorization: ApiKey <sua-chave-de-api>Este endpoint também aceita o token JWT, no formato Authorization: Bearer <accessToken>. Veja Token JWT.
Parâmetros
Parâmetro de query string, opcional.
| Parâmetro | Descrição | Obrigatório |
|---|---|---|
| returnsFaceInfo | Quando true, cada item de resources passa a trazer o objeto face com os dados da face usada na comparação, e metadata.filesInfo[].data vem vazio. Padrão: false. | Não |
Um parâmetro de query desconhecido, ou com valor de tipo inválido, faz a requisição falhar com 422.
Arquivos aceitos
| Item | Valor |
|---|---|
| Formatos | image/png, image/jpeg, application/pdf |
| Quantidade por chamada | 2 arquivos |
| Tamanho máximo (multipart) | 15 MB por arquivo |
Os nomes dos campos do formulário são livres — eles voltam na resposta, dentro de resources e de metadata.filesInfo, e é por eles que você identifica qual imagem é qual. Enviar mais de dois arquivos é recusado com 422.
Quando o arquivo é um PDF de várias páginas, cada página é analisada em ordem e a primeira página com alguma face detectada é a que entra na comparação; as demais são descartadas.
Além do multipart/form-data, os arquivos podem ser enviados em JSON, no campo base64, como descrito em Envio de arquivos.
Exemplo Request
curl -i -X POST 'https://api-homolog.nxcd.app/face-match/v2' \
--header 'Authorization: ApiKey SUA_CHAVE_AQUI' \
--form 'documento=@./cnh.jpg' \
--form 'selfie=@./selfie.jpg'Response
Os campos abaixo descrevem a resposta da v2, a versão suportada deste endpoint.
| Campo | Descrição | Tipo |
|---|---|---|
| id | Identificador único da requisição | String |
| version | Versão da API que atendeu a chamada | String |
| data | Lista com o resultado da comparação. Traz uma entrada quando a comparação aconteceu, e vem vazia quando não. | Object[] |
| data[].matched | true quando as duas faces comparadas são da mesma pessoa | Boolean |
| data[].confidence | Grau de confiança da comparação, de 0 a 100 | Number |
| data[].resources | As duas faces usadas na comparação, na ordem em que foram confrontadas | Object[] |
| data[].resources[].fieldname | Nome do campo em que aquele arquivo foi enviado | String |
| data[].resources[].page | Página do arquivo em que a face foi encontrada. Começa em 0; para imagens é sempre 0. | Number |
| data[].resources[].face | Dados da face usada na comparação. Só aparece com returnsFaceInfo=true. | Object |
| data[].resources[].face.type | SELFIE quando a face ocupa boa parte da imagem, ID quando é uma face pequena, típica de foto de documento | String |
| data[].resources[].face.confidence | Confiança com que o detector localizou a face | Number |
| data[].resources[].face.age | Idade aparente estimada | Number |
| data[].resources[].face.gender | Objeto com value (Male ou Female) e confidence | Object |
| data[].resources[].face.boundingBox | Posição da face na imagem, em proporções entre 0 e 1 (top, left, width, height) | Object |
| data[].resources[].face.croppedBase64 | Recorte da face em base64 | String |
| metadata | Objeto com os metadados da requisição | Object |
| metadata.filesInfo | Lista com uma entrada por arquivo enviado, mesmo os em que nenhuma face foi encontrada | Object[] |
| metadata.filesInfo[].fieldname | Nome do campo usado no envio | String |
| metadata.filesInfo[].name | Nome do arquivo enviado | String |
| metadata.filesInfo[].size | Tamanho do arquivo, em bytes | Number |
| metadata.filesInfo[].pages | Número de páginas do arquivo. Para imagens, 1. | Number |
| metadata.filesInfo[].mimetype | Tipo do arquivo enviado | String |
| metadata.filesInfo[].encoding | Codificação do arquivo no envio | String |
| metadata.filesInfo[].sha256 | Hash SHA-256 do arquivo enviado | String |
| metadata.filesInfo[].data | Faces encontradas naquele arquivo. Vem vazia quando returnsFaceInfo=true. | Object[] |
| metadata.filesInfo[].data[].page | Página em que a face foi encontrada, começando em 0 | Number |
| metadata.filesInfo[].data[].face | Objeto com boundingBox e croppedBase64 da face | Object |
| metadata.timeSpent | Tempo de processamento da requisição, em milissegundos | Number |
🚧 data vazio não é erro
A comparação só acontece quando dois arquivos têm alguma face detectada. Um arquivo sem face, um único arquivo enviado, ou nenhum arquivo: a resposta é 200 com "data": [], e metadata.filesInfo continua listando tudo o que foi enviado.
Isso significa que data[0] pode não existir. Verifique o tamanho de data antes de ler matched — um código que assume data[0].matched recebe um erro em vez de um resultado negativo.
Exemplos JSON
- Faces correspondentes
{
"id": "13cfec7c-b238-4820-a4d1-5173e4c1418e",
"version": "v2",
"data": [
{
"matched": true,
"confidence": 97.81,
"resources": [
{ "fieldname": "documento", "page": 0 },
{ "fieldname": "selfie", "page": 0 }
]
}
],
"metadata": {
"filesInfo": [
{
"fieldname": "documento",
"name": "cnh.jpg",
"size": 567098,
"pages": 1,
"mimetype": "image/jpeg",
"encoding": "7bit",
"sha256": "41c2ece339abfcf9ba1e7d5a60666162a24ef34ba95adeb41ed1d9721c91c6b0",
"data": [
{
"page": 0,
"face": {
"boundingBox": {
"top": 0.2237228155136108,
"left": 0.2856607735157013,
"width": 0.1555942893028259,
"height": 0.1687679588794708
},
"croppedBase64": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAJYCAMAAA...K5CYII="
}
}
]
},
{
"fieldname": "selfie",
"name": "selfie.jpg",
"size": 890098,
"pages": 1,
"mimetype": "image/jpeg",
"encoding": "7bit",
"sha256": "41c2ece339abfcf9ba1e7d5a60666162a24ef34ba95adeb41ed1d9721c91c6c4",
"data": [
{
"page": 0,
"face": {
"boundingBox": {
"top": 0.1142489537596702,
"left": 0.2693724930286407,
"width": 0.4232554733753204,
"height": 0.6968063116073608
},
"croppedBase64": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAJYCAMAAA...SuQmCC"
}
}
]
}
],
"timeSpent": 4820
}
}- Faces de pessoas diferentes
{
"id": "6a1d1e8c-4a7d-4a5e-9f2c-1b0f2a9c7e11",
"version": "v2",
"data": [
{
"matched": false,
"confidence": 12.4,
"resources": [
{ "fieldname": "documento", "page": 0 },
{ "fieldname": "selfie", "page": 0 }
]
}
],
"metadata": {
"filesInfo": [
{
"fieldname": "documento",
"name": "cnh.jpg",
"size": 567098,
"pages": 1,
"mimetype": "image/jpeg",
"encoding": "7bit",
"sha256": "41c2ece339abfcf9ba1e7d5a60666162a24ef34ba95adeb41ed1d9721c91c6b0",
"data": [
{
"page": 0,
"face": {
"boundingBox": { "top": 0.22, "left": 0.28, "width": 0.15, "height": 0.16 },
"croppedBase64": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAJYCAMAAA...K5CYII="
}
}
]
},
{
"fieldname": "selfie",
"name": "selfie.jpg",
"size": 890098,
"pages": 1,
"mimetype": "image/jpeg",
"encoding": "7bit",
"sha256": "41c2ece339abfcf9ba1e7d5a60666162a24ef34ba95adeb41ed1d9721c91c6c4",
"data": [
{
"page": 0,
"face": {
"boundingBox": { "top": 0.11, "left": 0.26, "width": 0.42, "height": 0.69 },
"croppedBase64": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAJYCAMAAA...SuQmCC"
}
}
]
}
],
"timeSpent": 4610
}
}- Nenhuma face encontrada em um dos arquivos
{
"id": "a7f0c3d2-9e51-4b8a-8f31-2d4c6b9e0a77",
"version": "v2",
"data": [],
"metadata": {
"filesInfo": [
{
"fieldname": "documento",
"name": "cnh.jpg",
"size": 567098,
"pages": 1,
"mimetype": "image/jpeg",
"encoding": "7bit",
"sha256": "41c2ece339abfcf9ba1e7d5a60666162a24ef34ba95adeb41ed1d9721c91c6b0",
"data": [
{
"page": 0,
"face": {
"boundingBox": { "top": 0.22, "left": 0.28, "width": 0.15, "height": 0.16 },
"croppedBase64": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAJYCAMAAA...K5CYII="
}
}
]
},
{
"fieldname": "selfie",
"name": "borrada.jpg",
"size": 152064,
"pages": 1,
"mimetype": "image/jpeg",
"encoding": "7bit",
"sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
"data": []
}
],
"timeSpent": 3120
}
}- Com
returnsFaceInfo=true
{
"id": "b91e5f44-1c02-4a77-9d3e-58a0f2c4e6bb",
"version": "v2",
"data": [
{
"matched": true,
"confidence": 96.32,
"resources": [
{
"fieldname": "documento",
"page": 0,
"face": {
"confidence": 0.98,
"type": "ID",
"age": 34,
"gender": { "value": "Male", "confidence": 99.72 },
"boundingBox": { "top": 0.22, "left": 0.28, "width": 0.15, "height": 0.16 },
"croppedBase64": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAJYCAMAAA...K5CYII="
}
},
{
"fieldname": "selfie",
"page": 0,
"face": {
"confidence": 0.99,
"type": "SELFIE",
"age": 33,
"gender": { "value": "Male", "confidence": 99.15 },
"boundingBox": { "top": 0.11, "left": 0.26, "width": 0.42, "height": 0.69 },
"croppedBase64": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAJYCAMAAA...SuQmCC"
}
}
]
}
],
"metadata": {
"filesInfo": [
{
"fieldname": "documento",
"name": "cnh.jpg",
"size": 567098,
"pages": 1,
"mimetype": "image/jpeg",
"encoding": "7bit",
"sha256": "41c2ece339abfcf9ba1e7d5a60666162a24ef34ba95adeb41ed1d9721c91c6b0",
"data": []
},
{
"fieldname": "selfie",
"name": "selfie.jpg",
"size": 890098,
"pages": 1,
"mimetype": "image/jpeg",
"encoding": "7bit",
"sha256": "41c2ece339abfcf9ba1e7d5a60666162a24ef34ba95adeb41ed1d9721c91c6c4",
"data": []
}
],
"timeSpent": 4930
}
}Headers de resposta
| Header | Quando aparece |
|---|---|
Nextid-ReqId | Em todas as respostas. Traz o identificador da requisição, o mesmo do campo id do corpo. |
Erros
| Código | Quando ocorre |
|---|---|
| 401 Unauthorized | Chave de API ou token JWT ausente, inválido, ou sem permissão para este endpoint. |
| 404 Not Found | A versão informada na URL não existe. |
| 406 Not Acceptable | O corpo foi enviado como JSON, mas sem o campo base64. |
| 413 Payload Too Large | Um dos arquivos enviados em multipart/form-data passa de 15 MB. |
| 415 Unsupported Media Type | O header Content-Type está ausente ou não é suportado. |
| 422 Unprocessable Entity | Parâmetro de query desconhecido ou com valor inválido, formato de arquivo não aceito, ou mais de dois arquivos. |
| 500 Internal Server Error | Falha inesperada durante o processamento. |
O formato das respostas de erro está descrito em Códigos HTTP das respostas.
Versões
| Versão | Situação | O que muda |
|---|---|---|
| v2 | Recomendada | data é uma lista com uma comparação, que traz confidence. |
A v2 é a versão suportada deste endpoint. Ela atende tanto /face-match/v2 quanto /face-match sem versão na URL.