Face Match
This endpoint takes two images, locates one face in each, and answers whether the two faces belong to the same person.
The most common use is checking a selfie against the photo printed on an identity document, but the comparison does not require that: any pair of images with a detectable face works — two selfies, two documents, or one of each.
🚧 Face Match is not Face
- Face Match (this page) — takes two images and answers whether they are the same person.
- Face — takes one image and only describes the faces that exist in it, comparing nothing.
This endpoint does not perform liveness — it does not tell a person present at the capture apart from a photo of a photo. For that, use Liveness. And it does not query official databases: if you need to check the face against the government's biometrics for a CPF (the Brazilian individual taxpayer number), use Face Match + Datavalid.
TIP
If the selfie comes from a liveness session made with our SDKs, prefer Face Match for Liveness: it reuses the selfie already captured, and you send only the document.
Request
POST/face-match/v2Headers
Authorization: ApiKey <your-api-key>This endpoint also accepts the JWT token, in the format Authorization: Bearer <accessToken>. See JWT Token.
Parameters
Query string parameter, optional.
| Parameter | Description | Required |
|---|---|---|
| returnsFaceInfo | When true, each item of resources also carries the face object with the data of the face used in the comparison, and metadata.filesInfo[].data comes back empty. Default: false. | No |
An unknown query parameter, or one whose value has the wrong type, makes the request fail with 422.
Accepted files
| Item | Value |
|---|---|
| Formats | image/png, image/jpeg, application/pdf |
| Files per call | 2 files |
| Maximum size (multipart) | 15 MB per file |
The form field names are up to you — they come back in the response, inside resources and metadata.filesInfo, and they are how you tell which image is which. Sending more than two files is rejected with 422.
When the file is a multi-page PDF, each page is analyzed in order and the first page with any face detected is the one that goes into the comparison; the rest are discarded.
Besides multipart/form-data, the files can be sent as JSON, in the base64 field, as described in Sending files.
Example request
curl -i -X POST 'https://api-homolog.nxcd.app/face-match/v2' \
--header 'Authorization: ApiKey YOUR_API_KEY' \
--form 'documento=@./cnh.jpg' \
--form 'selfie=@./selfie.jpg'Response
The fields below describe the v2 response, the supported version of this endpoint.
| Field | Description | Type |
|---|---|---|
| id | Unique identifier of the request | String |
| version | API version that served the call | String |
| data | List with the result of the comparison. Carries one entry when the comparison happened, and comes back empty when it did not. | Object[] |
| data[].matched | true when the two faces compared belong to the same person | Boolean |
| data[].confidence | Confidence level of the comparison, from 0 to 100 | Number |
| data[].resources | The two faces used in the comparison, in the order in which they were checked | Object[] |
| data[].resources[].fieldname | Name of the field that file was sent in | String |
| data[].resources[].page | Page of the file where the face was found. Starts at 0; for images it is always 0. | Number |
| data[].resources[].face | Data of the face used in the comparison. Only appears with returnsFaceInfo=true. | Object |
| data[].resources[].face.type | SELFIE when the face takes up much of the image, ID when it is a small face, typical of a document photo | String |
| data[].resources[].face.confidence | Confidence with which the detector located the face | Number |
| data[].resources[].face.age | Estimated apparent age | Number |
| data[].resources[].face.gender | Object with value (Male or Female) and confidence | Object |
| data[].resources[].face.boundingBox | Position of the face in the image, in proportions between 0 and 1 (top, left, width, height) | Object |
| data[].resources[].face.croppedBase64 | Crop of the face in base64 | String |
| metadata | Object with the metadata of the request | Object |
| metadata.filesInfo | List with one entry per file sent, including those where no face was found | Object[] |
| metadata.filesInfo[].fieldname | Name of the field used to send the file | String |
| metadata.filesInfo[].name | Name of the file sent | String |
| metadata.filesInfo[].size | Size of the file, in bytes | Number |
| metadata.filesInfo[].pages | Number of pages of the file. For images, 1. | Number |
| metadata.filesInfo[].mimetype | Type of the file sent | String |
| metadata.filesInfo[].encoding | Encoding of the file as sent | String |
| metadata.filesInfo[].sha256 | SHA-256 hash of the file sent | String |
| metadata.filesInfo[].data | Faces found in that file. Comes back empty when returnsFaceInfo=true. | Object[] |
| metadata.filesInfo[].data[].page | Page where the face was found, starting at 0 | Number |
| metadata.filesInfo[].data[].face | Object with the boundingBox and croppedBase64 of the face | Object |
| metadata.timeSpent | Processing time of the request, in milliseconds | Number |
🚧 An empty data is not an error
The comparison only happens when two files have some face detected. A file with no face, a single file sent, or no file at all: the response is 200 with "data": [], and metadata.filesInfo still lists everything that was sent.
That means data[0] may not exist. Check the size of data before reading matched — code that assumes data[0].matched gets an error instead of a negative result.
JSON examples
- Matching faces
{
"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 of different people
{
"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
}
}- No face found in one of the files
{
"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
}
}- With
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
}
}Response headers
| Header | When it appears |
|---|---|
Nextid-ReqId | On every response. Carries the request identifier, the same one as the id field in the body. |
Errors
| Code | When it happens |
|---|---|
| 401 Unauthorized | API key or JWT token missing, invalid, or without permission for this endpoint. |
| 404 Not Found | The version given in the URL does not exist. |
| 406 Not Acceptable | The body was sent as JSON, but without the base64 field. |
| 413 Payload Too Large | One of the files sent as multipart/form-data is larger than 15 MB. |
| 415 Unsupported Media Type | The Content-Type header is missing or is not supported. |
| 422 Unprocessable Entity | An unknown query parameter or one with an invalid value, a file format that is not accepted, or more than two files. |
| 500 Internal Server Error | Unexpected failure during processing. |
The error response format is described in HTTP response codes.
Versions
| Version | Status | What changes |
|---|---|---|
| v2 | Recommended | data is a list with one comparison, which carries confidence. |
v2 is the supported version of this endpoint. It serves both /face-match/v2 and /face-match without a version in the URL.