Face Match + Datavalid
This endpoint does two things in one call:
- Face Match — takes two images, locates one face in each, and compares the two with each other, exactly like Face Match.
- Datavalid — sends one of those faces to Datavalid, the validation service run by SERPRO (the Brazilian federal data processing agency), and asks whether it matches the official biometrics on record for the CPF (the Brazilian individual taxpayer number) given in the URL.
The difference from plain Face Match is the second step: here the face is checked not only against the other image you sent, but also against the government's official database. That is what makes it possible to answer "this person really is the holder of this CPF", and not just "these two photos are of the same person".
This endpoint does not perform liveness (use Liveness) and does not extract the data written on the document (that is what OCR is for).
Request
POST/face-match-and-datavalid/v2/natural-person/{CPF}Headers
Authorization: ApiKey <your-api-key>This endpoint also accepts the JWT token, in the format Authorization: Bearer <accessToken>. See JWT Token.
SERPRO keys
The Datavalid query made by this endpoint uses our SERPRO access. You do not need a contract of your own with SERPRO to use it, and it does not read customer keys from the request: the x-customer-key and x-customer-secret headers, if sent here, are ignored.
TIP
Sending your own SERPRO keys is supported on the /full-ocr-and-datavalid endpoint, not on this one. If your contract provides for using your own keys, talk to our support team before building the integration here.
Parameters
| Parameter | Description | Required |
|---|---|---|
| CPF | The CPF whose biometrics will be queried in Datavalid. Goes in the URL path, with or without formatting. It is validated by its check digits. | Yes |
| faceToDatavalid | Which face to send to Datavalid: IdCard or Selfie. Query string. Default: IdCard. | No |
| returnsFaceInfo | When present, each item of resources also carries the face object, and metadata.filesInfo[].data comes back empty. Query string. Default: off. | No |
🚧 faceToDatavalid still applies, and it is a preference, not a guarantee
The API classifies each face found into two types, by how much of the image it takes up: large faces become SELFIE, small faces — typical of a photo printed on a document — become ID.
faceToDatavalid=IdCard(the default): sends the first face of typeIDto Datavalid. If there is none, it sends the face of typeSELFIE.faceToDatavalid=Selfie: sends the first face of typeSELFIE. If there is none, it sends the face of typeID.
In other words, the Datavalid query happens either way, with whichever face is available. A value outside IdCard and Selfie makes the request fail with 422.
‼️ returnsFaceInfo=false turns the parameter ON
Unlike Face Match, here the value of returnsFaceInfo is not interpreted: the mere presence of the parameter in the query string already activates it, returnsFaceInfo=false included. To leave it off, do not send the parameter at all.
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, in resources and in metadata.filesInfo. Sending more than two files is rejected with 422.
In multi-page PDFs, each page is analyzed in order and the first one with any face detected is the one that goes into the analysis.
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-and-datavalid/v2/natural-person/12345678909' \
--header 'Authorization: ApiKey YOUR_API_KEY' \
--form 'documento=@./cnh.jpg' \
--form 'selfie=@./selfie.jpg'Choosing the selfie as the face to validate in Datavalid:
curl -i -X POST 'https://api-homolog.nxcd.app/face-match-and-datavalid/v2/natural-person/123.456.789-09?faceToDatavalid=Selfie' \
--header 'Authorization: ApiKey YOUR_API_KEY' \
--form 'documento=@./cnh.jpg' \
--form 'selfie=@./selfie.jpg'Response
| 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 analysis. Carries one entry when the comparison happened, and comes back empty when it did not. | Object[] |
| data[].matched | true when the two faces sent belong to the same person. This is the result of the face match, not of Datavalid. | Boolean |
| data[].confidence | Confidence level of the comparison between the two faces, from 0 to 100 | Number |
| data[].datavalid | Result of the Datavalid query. Comes back as {} when there was no query. | Object |
| data[].datavalid.federalRevenueNumberAvailability | Indicates whether the CPF given exists in the official database | Boolean |
| data[].datavalid.federalRevenueNumberStatus | Indicates whether the CPF's registration status in the official database is regular | Boolean |
| data[].datavalid.face | Result of comparing the face sent with the official biometrics. Comes back as {} when there are no biometrics to compare. | Object |
| data[].datavalid.face.availability | Indicates whether facial biometrics are on record for that CPF in the official database | Boolean |
| data[].datavalid.face.similarity | Similarity between the face sent and the official biometrics, on a scale from 0 to 1 | Number |
| data[].datavalid.face.probability | Probability band of the face sent being the same as the one in the official database: VeryHigh, High, Low or VeryLow | String |
| 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. Only appears when returnsFaceInfo is sent. | Object |
| data[].resources[].face.type | SELFIE or ID, following the classification described above | 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 | 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, each with page and face (boundingBox and croppedBase64). Comes back empty when returnsFaceInfo is sent. | Object[] |
| metadata.timeSpent | Processing time of the request, in milliseconds | Number |
🚧 matched and datavalid answer different questions
matched says whether the two images you sent are of the same person. datavalid.face says whether the face chosen matches the official biometrics for the CPF.
The two can disagree, and the Datavalid query is made even when matched is false — that step is not conditional on the result of the comparison. Treat the two fields as independent signals in your decision.
‼️ No two faces, no Datavalid
The analysis only happens when two files have some face detected. If one of them does not, the response is 200 with "data": [] — no matched, no confidence, and no Datavalid query at all.
Check the size of data before reading data[0].
JSON examples
- Matching faces and biometrics confirmed in Datavalid
{
"id": "13cfec7c-b238-4820-a4d1-5173e4c1418e",
"version": "v2",
"data": [
{
"matched": true,
"confidence": 97.81,
"datavalid": {
"federalRevenueNumberAvailability": true,
"federalRevenueNumberStatus": true,
"face": {
"availability": true,
"similarity": 0.96,
"probability": "VeryHigh"
}
},
"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": 7340
}
}- Matching faces, but the CPF has no biometrics in the official database
{
"id": "6a1d1e8c-4a7d-4a5e-9f2c-1b0f2a9c7e11",
"version": "v2",
"data": [
{
"matched": true,
"confidence": 95.12,
"datavalid": {
"federalRevenueNumberAvailability": true,
"federalRevenueNumberStatus": true,
"face": {
"availability": false
}
},
"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": 6980
}
}- 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
}
}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. Only v2 is accepted. |
| 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 | A CPF that fails check-digit validation, a faceToDatavalid outside the enum, a file format that is not accepted, or more than two files. It is also the code returned when Datavalid rejects the face for poor image quality. |
| 500 Internal Server Error | Unexpected failure during processing, including Datavalid being unavailable. |
Example response with an invalid CPF:
Status Code: 422{
"id": "13cfec7c-b238-4820-a4d1-5173e4c1418e",
"error": {
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Invalid Federal Revenue Number"
}
}Example response when Datavalid rejects the face for quality:
Status Code: 422{
"id": "6a1d1e8c-4a7d-4a5e-9f2c-1b0f2a9c7e11",
"error": {
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "The face is of poor quality to be processed in the datavalid"
}
}TIP
A face rejected for quality brings down the whole request with 422 — you do not get the face match result along with it. If you need the face match even when Datavalid cannot assess the image, make the two calls separately, through Face Match.
The error response format is described in HTTP response codes.
Versions
This endpoint has a single version, v2. It is the default when the version is omitted from the URL.
| Version | Status | What changes |
|---|---|---|
| v2 | Recommended | The only version. Serves both /face-match-and-datavalid/natural-person/{CPF} and /face-match-and-datavalid/v2/natural-person/{CPF}. |
Any other value in the URL — /face-match-and-datavalid/v3/natural-person/{CPF}, for example — returns 404.