Face Validation
This endpoint answers a single question: does the face in the image sent match the official biometrics on record for the CPF (the Brazilian individual taxpayer number) given in the URL?
You send one image and a CPF. The API locates the face in the image, crops it, and checks it against the official database. The response is a lean verdict — no comparison between two of your own photos, no registration data, and no document extraction.
This endpoint does not compare two images with each other (that is what Face Match is for), does not perform liveness (use Liveness), does not extract the data written on the document, and does not return the registration data of the CPF (use the Individual Bureau).
Face Validation or Face Match + Datavalid
Both endpoints validate a face against the official database from a CPF. The difference is in what else they do, and in the shape of the verdict.
| Face Validation | Face Match + Datavalid | |
|---|---|---|
| Route | /face-validation/v1/natural-person/{CPF} | /face-match-and-datavalid/v2/natural-person/{CPF} |
| Files per call | 1 image | 2 files |
| Compares your images with each other | No | Yes, returns matched and confidence |
| Validates against the official database | Yes | Yes |
| Accepted formats | image/png, image/jpeg | image/png, image/jpeg, application/pdf |
Sending as JSON (base64) | Accepted | Accepted |
similarity field | Not returned | Returned |
| Probability band | VeryHigh, High, Low, VeryLow, Unknown | VeryHigh, High, Low, VeryLow |
| Registration data of the CPF | Not returned | Returns federalRevenueNumberStatus |
Format of data | Object | List |
| Permission | nextid.bureaus.faceValidation | nextid.bureaus.faceMatchAndDatavalid |
Use Face Validation when you have only one image — a selfie, for example — and all you need to know is whether it belongs to the holder of the CPF. Use Face Match + Datavalid when you also need to check two of your own images against each other, or when you need the registration status of the CPF in the same call.
Request
POST/face-validation/v1/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.
The call requires the nextid.bureaus.faceValidation permission. It is a permission of its own: whoever calls Face Match + Datavalid does not normally gain access to this endpoint because of it.
Parameters
| Parameter | Description | Required |
|---|---|---|
| CPF | The CPF whose biometrics will be queried. Goes in the URL path, with or without formatting. It is validated by its check digits before any query takes place. | Yes |
| returnsFaceInfo | When true, the face object in the response gains boundingBox and croppedBase64. Query string. Default: false. | No |
The CPF can be sent with or without formatting (123.456.789-09 or 12345678909), always with all 11 digits, including leading zeros.
🚧 The query string of this endpoint is strict
Unlike other endpoints, here the query string is validated:
returnsFaceInfoaccepts onlytrueorfalse. Any other encoding —1,0,TRUE,yes— is rejected with422, rather than silently ignored.- An unknown parameter brings the request down.
?returnsFaceInfoo=true, with the typo, returns422instead of processing the call without the face info.
This is deliberate: a misspelled parameter fails loudly, instead of returning 200 with a response that is not the one you asked for.
Accepted files
| Item | Value |
|---|---|
| Formats | image/png, image/jpeg |
| Files per call | 1 file |
| Ways to send | multipart/form-data or base64 in JSON |
| Maximum size (multipart) | 15 MB |
The image can be sent as multipart/form-data or in base64, in the base64 field of the JSON body, as described in Sending files. Both ways behave the same: the same formats, the same limit of one file per call, and the same rejections.
The field name — the form field in multipart, or the key inside base64 in JSON — is up to you, and it comes back in the response, in metadata.filesInfo. Sending more than one file is rejected with 422, and so is sending none.
‼️ The urls field is not accepted on this endpoint
Sending the file by URL is not accepted here. A JSON body carrying the urls field is rejected with 422, before the file is ever fetched:
{
"id": "e2b0f3a7-5c41-4c2b-9d33-8a5f1c7e4b02",
"error": {
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Sending files by URL is not supported. Use multipart/form-data or the \"base64\" field."
}
}PNG and JPEG only, in both ways of sending. Any other mimetype — PDF included — returns 422, and so does a submission with more than one file.
If the image has more than one face, the one with the highest confidence is the one used in the validation. If no face is detected, the request fails with 422 — the query to the official database never happens.
Example request
curl -i -X POST 'https://api-homolog.nxcd.app/face-validation/v1/natural-person/12345678909' \
--header 'Authorization: ApiKey YOUR_API_KEY' \
--form 'selfie=@./selfie.jpg'Also asking for the crop of the face found:
curl -i -X POST 'https://api-homolog.nxcd.app/face-validation/v1/natural-person/123.456.789-09?returnsFaceInfo=true' \
--header 'Authorization: ApiKey YOUR_API_KEY' \
--form 'selfie=@./selfie.jpg'The same call, with the image in base64:
curl -i -X POST 'https://api-homolog.nxcd.app/face-validation/v1/natural-person/12345678909' \
--header 'Authorization: ApiKey YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{ "base64": { "selfie": "BASE_64_HERE" } }'Response
| Field | Description | Type |
|---|---|---|
| id | Unique identifier of the request | String |
| version | API version that served the call | String |
| data | Object with the result of the validation. It is not a list. | Object |
| data.federalRevenueNumber | The CPF queried, without formatting | String |
| data.face | The verdict of the validation | Object |
| data.face.availability | true when there was enough of a face to assess. false when there was no verdict. | Boolean |
| data.face.probability | Probability band of the face sent belonging to the holder of the CPF: VeryHigh, High, Low, VeryLow or Unknown | String |
| data.face.boundingBox | Position of the face in the image sent, in proportions between 0 and 1. Only appears with returnsFaceInfo=true. | Object |
| data.face.croppedBase64 | Crop of the face in base64. Only appears with returnsFaceInfo=true. | String |
| metadata | Object with the metadata of the request | Object |
| metadata.filesInfo | List with a single entry, for the file sent | 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 sent, in bytes | Number |
| metadata.filesInfo[].pages | Number of pages of the file. Since only images are accepted, it is always 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.timeSpent | Processing time of the request, in milliseconds | Number |
boundingBox carries top, left, width and height as proportions of the image, between 0 and 1 — not in pixels. The origin is the top-left corner: left and top are the distance from the face to that corner, divided by the width and the height of the image respectively. To get pixels, multiply left and width by the width of the image, and top and height by its height.
The values of probability
availability | probability | What it means |
|---|---|---|
true | VeryHigh | Assessed. Very high probability of being the holder of the CPF. |
true | High | Assessed. High probability. |
true | Low | Assessed. Low probability. |
true | VeryLow | Assessed. Very low probability. |
false | Unknown | Not assessed. There was not enough of a face to produce a verdict. |
‼️ Unknown is not a bad result — it is the absence of a result
This is the point that gets misread most often. Unknown and VeryLow are not the same thing, and the difference changes the decision you make:
VeryLow(withavailability: true) is a negative verdict: the face was compared with the official database, and the result is that it probably is not the holder of the CPF.Unknown(withavailability: false) is no verdict at all: there was not enough of a face to assess. The API is not saying the person is not the holder — it is saying it does not know.
Treating Unknown as a rejection denies access to legitimate people over an image or database problem. Treating Unknown as an approval lets anyone through. The right thing is to treat it as a third path: ask for a new capture, or send it for manual review.
In practice, test availability before looking at probability.
🚧 This endpoint does not return similarity
If you are coming from Face Match + Datavalid, note that the similarity field does not exist here, and it is not going to start existing.
The validation used by this endpoint produces a risk band, not a continuous measure. There is no number from 0 to 1 behind probability that was ever measured. Exposing one would be inventing precision the data does not have.
Do not build business rules expecting a continuous score here: decide on the probability bands, which are the real data.
JSON examples
- Face validated with very high probability
{
"id": "13cfec7c-b238-4820-a4d1-5173e4c1418e",
"version": "v1",
"data": {
"federalRevenueNumber": "12345678909",
"face": {
"availability": true,
"probability": "VeryHigh"
}
},
"metadata": {
"filesInfo": [
{
"fieldname": "selfie",
"name": "selfie.jpg",
"size": 890098,
"pages": 1,
"mimetype": "image/jpeg",
"encoding": "7bit",
"sha256": "41c2ece339abfcf9ba1e7d5a60666162a24ef34ba95adeb41ed1d9721c91c6c4"
}
],
"timeSpent": 4210
}
}- No verdict — there was not enough of a face to assess
{
"id": "6a1d1e8c-4a7d-4a5e-9f2c-1b0f2a9c7e11",
"version": "v1",
"data": {
"federalRevenueNumber": "12345678909",
"face": {
"availability": false,
"probability": "Unknown"
}
},
"metadata": {
"filesInfo": [
{
"fieldname": "selfie",
"name": "selfie.jpg",
"size": 762310,
"pages": 1,
"mimetype": "image/jpeg",
"encoding": "7bit",
"sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
}
],
"timeSpent": 3980
}
}TIP
This is a 200, not an error. The request was processed; what did not happen was a verdict. See the block above on how to handle Unknown.
- With
returnsFaceInfo=true
{
"id": "a7f0c3d2-9e51-4b8a-8f31-2d4c6b9e0a77",
"version": "v1",
"data": {
"federalRevenueNumber": "12345678909",
"face": {
"availability": true,
"probability": "High",
"boundingBox": { "top": 0.11, "left": 0.26, "width": 0.42, "height": 0.69 },
"croppedBase64": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAJYCAMAAA...K5CYII="
}
},
"metadata": {
"filesInfo": [
{
"fieldname": "selfie",
"name": "selfie.png",
"size": 1204877,
"pages": 1,
"mimetype": "image/png",
"encoding": "7bit",
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
}
],
"timeSpent": 5120
}
}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 the nextid.bureaus.faceValidation permission. |
| 404 Not Found | The version given in the URL does not exist. Only v1 is accepted. |
| 406 Not Acceptable | The body was sent as JSON, but without the base64 field. |
| 413 Payload Too Large | The file sent is larger than 15 MB. |
| 415 Unsupported Media Type | The Content-Type header is missing, or is neither multipart/form-data nor application/json. |
| 422 Unprocessable Entity | A CPF that fails check-digit validation; the urls field in the body; no file sent; more than one file; a file format that is not accepted; an unknown query parameter or one with an invalid value; no face detected in the image; a face rejected for low quality. |
| 500 Internal Server Error | Unexpected failure during processing, including the official database being unavailable. |
TIP
This endpoint hardly uses 400 at all. 422 is the rejection code for every problem with what was sent — from the CPF to the file format, query string included.
The file-related rejections come back with 422 and one of these messages, the same in both ways of sending:
message | When it happens |
|---|---|
Sending files by URL is not supported. Use multipart/form-data or the "base64" field. | The body carries the urls field. |
An image file is required. | No file was sent. |
Exceed limit of files. Max allowed 1. | More than one file in the same call. |
Expected one of the following mimetypes: image/png, image/jpeg | The file is neither PNG nor JPEG. PDF included. |
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 no face is detected in the image:
Status Code: 422{
"id": "6a1d1e8c-4a7d-4a5e-9f2c-1b0f2a9c7e11",
"error": {
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "No face detected in the image"
}
}🚧 No face in the image is 422, not Unknown
These are different situations, and they arrive by different routes:
- We found no face in the image you sent →
422, with the message above. The validation never even runs. - We found the face, but there was no way to produce a verdict →
200, withavailability: falseandprobability: "Unknown".
In the first case the problem is in the image sent, and asking for a new capture usually solves it.
The error response format is described in HTTP response codes.
Versions
This endpoint has a single version, v1. It is the default when the version is omitted from the URL.
| Version | Status | What changes |
|---|---|---|
| v1 | Recommended | The only version. Serves both /face-validation/natural-person/{CPF} and /face-validation/v1/natural-person/{CPF}. |
Any other value in the URL — /face-validation/v2/natural-person/{CPF}, for example — returns 404.