Cadastre imagens de referência facial para verificação biométrica em transações futuras.
O enrollment biométrico permite pré-cadastrar a imagem de referência facial de um usuário.
Essa imagem é utilizada nas etapas BIOMETRIC_MATCH e DOCUMENT_PHOTO_MATCH
para comparar o rosto capturado em tempo real com a referência armazenada.
O enrollment é versionado — cada chamada incrementa a versão, permitindo atualizar a foto de referência sem perder o histórico.
PUT /v1/users/{userExternalId}/enrollment
Content-Type: application/json
Authorization: Bearer {access_token}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
image |
string (base64) | Sim | Imagem facial em base64 (JPEG). Para DOCUMENT_PHOTO, envie a foto completa do documento. |
cpf |
string | Sim | CPF do titular (11 dígitos, sem pontuação). Vincula o enrollment à identidade do usuário. |
source |
string | Não | Origem da imagem. Padrão: ORGANIZATION_PROVIDED. |
Foto vinda dos registros da própria organização — crachá, ficha de RH, folha de pagamento. O titular não estava presente na captura; quem responde pela imagem é a organização. A imagem deve conter exatamente um rosto visível.
BANK_PROVIDED era o nome anterior deste valor, de quando o único cliente era um banco. Continua aceito e é normalizado na gravação: o que volta na resposta e o que vai para a evidência é sempre ORGANIZATION_PROVIDED.
curl -X PUT "$BASE_URL/v1/users/usr_maria_001/enrollment" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"image": "'$(base64 -w0 selfie.jpg)'",
"cpf": "12345678909",
"source": "ORGANIZATION_PROVIDED"
}'
Imagem capturada na primeira sessão de liveness do usuário. Útil quando a organização não tem foto prévia e quer usar a primeira verificação facial como referência. Diferente da anterior, aqui o titular estava presente e a captura foi verificada ao vivo.
curl -X PUT "$BASE_URL/v1/users/usr_maria_001/enrollment" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"image": "'$(base64 -w0 liveness-frame.jpg)'",
"cpf": "12345678909",
"source": "FIRST_LIVENESS"
}'
Foto de documento de identidade (CNH, RG ou passaporte). O sistema extrai automaticamente a face do documento e a utiliza como imagem de enrollment.
Feature flag requerida: Esta fonte requer que a flag documentExtractionEnabled
esteja habilitada no tenant. Solicite a ativação ao seu gerente de conta.
curl -X PUT "$BASE_URL/v1/users/usr_maria_001/enrollment" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"image": "'$(base64 -w0 cnh-frente.jpg)'",
"cpf": "12345678909",
"source": "DOCUMENT_PHOTO"
}'
Neste fluxo, o servidor:
DetectFacesdocumentImageHash e extractionConfidenceSem foto prévia? Uma sessão de assinatura pode carregar a própria imagem de referência, em
referenceImage.contentna criação, sem passar por enrollment. Ela vale só para aquela transação e não cria cadastro. Veja Imagem de referência.
| Campo | Tipo | Descrição |
|---|---|---|
userExternalId |
string | Identificador externo do usuário. |
enrollmentHash |
string | Hash SHA-256 da imagem de enrollment (face crop quando DOCUMENT_PHOTO). |
enrollmentVersion |
integer | Versão do enrollment (incrementada a cada atualização). |
enrollmentSource |
string | Origem do enrollment: ORGANIZATION_PROVIDED, FIRST_LIVENESS ou DOCUMENT_PHOTO. |
enrolledAt |
string (ISO 8601) | Data/hora do enrollment. |
cpf |
string | CPF do titular. |
faceConfidence |
number | Confiança da detecção facial (0-100%). |
documentImageHash |
string | Hash SHA-256 do documento original. Presente apenas quando enrollmentSource é DOCUMENT_PHOTO. |
extractionConfidence |
number | Confiança da extração facial do documento (0-100%). Presente apenas quando enrollmentSource é DOCUMENT_PHOTO. |
{
"userExternalId": "usr_maria_001",
"enrollmentHash": "a1b2c3d4e5f6...",
"enrollmentVersion": 1,
"enrollmentSource": "ORGANIZATION_PROVIDED",
"enrolledAt": "2026-02-28T10:30:00.000Z",
"cpf": "12345678909",
"faceConfidence": 99.8
}
{
"userExternalId": "usr_maria_001",
"enrollmentHash": "f6e5d4c3b2a1...",
"enrollmentVersion": 2,
"enrollmentSource": "DOCUMENT_PHOTO",
"enrolledAt": "2026-02-28T10:35:00.000Z",
"cpf": "12345678909",
"faceConfidence": 98.5,
"documentImageHash": "9a8b7c6d5e4f...",
"extractionConfidence": 97.2
}
Todas as imagens passam por validação via DetectFaces para garantir:
Caso a validação falhe, a API retorna 422 Unprocessable Entity com o motivo específico.
dryRun)Enviar dryRun: true avalia a foto e não grava nada: nenhum cadastro é
criado, nenhuma imagem vai para o S3, nenhuma cota é consumida. Serve para conferir um lote de fotos
antes de comprometer o cadastro.
PUT /v1/users/matricula-4471/enrollment
Content-Type: application/json
Authorization: Bearer {access_token}
{
"dryRun": true,
"image": "<base64>",
"cpf": "12345678900"
}
Funciona igual em produção e em HML — não é recurso de sandbox.
const report = await client.users.enroll('matricula-4471', {
image: imageBase64,
cpf: '12345678900',
dryRun: true,
});
if (report.referenceQuality === 'rejected') {
// Nada foi gravado: peça outra foto antes de cadastrar.
console.warn(report.warnings);
}
report = client.users.enroll(
"matricula-4471",
image=image_base64,
cpf="12345678900",
dry_run=True,
)
if report.reference_quality == "rejected":
# Nada foi gravado: peça outra foto antes de cadastrar.
print(report.warnings)
report, _ := client.Users.Enroll(ctx, "matricula-4471", &signdocs.EnrollUserRequest{
Image: imageBase64,
CPF: "12345678900",
DryRun: true,
})
if report.ReferenceQuality == "rejected" {
// Nada foi gravado: peça outra foto antes de cadastrar.
fmt.Println(report.Warnings)
}
EnrollUserRequest req = new EnrollUserRequest(imageBase64, "12345678900");
req.setDryRun(true);
EnrollUserResponse report = client.users().enroll("matricula-4471", req);
if ("rejected".equals(report.getReferenceQuality())) {
// Nada foi gravado: peça outra foto antes de cadastrar.
System.out.println(report.getWarnings());
}
$report = $client->users->enroll('matricula-4471', new EnrollUserRequest(
image: $imageBase64,
cpf: '12345678900',
dryRun: true,
));
if ($report->referenceQuality === 'rejected') {
// Nada foi gravado: peça outra foto antes de cadastrar.
print_r($report->warnings);
}
var report = await client.Users.EnrollAsync("matricula-4471", new EnrollUserRequest(
Image: imageBase64,
Cpf: "12345678900",
DryRun: true));
if (report?.ReferenceQuality == "rejected")
{
// Nada foi gravado: peça outra foto antes de cadastrar.
Console.WriteLine(string.Join(", ", report.Warnings ?? []));
}
referenceQuality vem em toda resposta de cadastro — individual, em lote,
gravação real e dryRun — com um de três valores:
| Valor | Significado | O que fazer |
|---|---|---|
usable | Serve como referência. | Nada. |
marginal | Grava, mas com ressalvas em warnings. | Vale repetir a foto se for barato. |
rejected | Não serve. | Tirar outra foto antes de cadastrar. |
Não confunda com status. Numa linha de lote, status diz o que
aconteceu com a gravação (enrolled / failed), que é outra pergunta.
Uma foto ruim que gravou bem é status: enrolled com
referenceQuality: marginal — a combinação que vale agir, e que um campo só
não conseguiria expressar.
Também não confunda com faceConfidence. Esse responde
"isto é um rosto?", e satura perto de 100 em foto boa e em foto inútil do mesmo jeito:
uma imagem com brilho 15 e nitidez 13 cadastra com 99,99 de confiança e só falha na
comparação, meses depois.
{
"userExternalId": "matricula-4471",
"status": "enrolled",
"referenceQuality": "marginal",
"warnings": ["LOW_BRIGHTNESS"],
"quality": { "brightness": 22.4, "sharpness": 61.8 },
"pose": { "yaw": -4.1, "pitch": 2.7, "roll": 0.9 },
"faceCoverage": 0.11,
"faceConfidence": 99.99,
"enrollmentVersion": 1,
"expiresAt": "2026-12-01T00:00:00.000Z"
}
Para onboarding de muitos funcionários de uma vez. Até 25 por requisição, com sucesso parcial: uma linha ruim não derruba as outras.
POST /v1/users/enrollments
Content-Type: application/json
Authorization: Bearer {access_token}
{
"dryRun": false,
"enrollments": [
{ "userExternalId": "matricula-4471", "image": "<base64>", "cpf": "12345678900" },
{ "userExternalId": "matricula-4472", "image": "<base64>", "cpf": "98765432100" }
]
}
A resposta traz o resumo e uma linha por item, na ordem enviada:
{
"submitted": 2, "succeeded": 2, "failed": 0, "dryRun": false,
"usable": 1, "marginal": 1, "rejected": 0,
"results": [
{ "index": 0, "userExternalId": "matricula-4471", "status": "enrolled",
"referenceQuality": "usable", "warnings": [], "enrollmentVersion": 1 },
{ "index": 1, "userExternalId": "matricula-4472", "status": "enrolled",
"referenceQuality": "marginal", "warnings": ["SMALL_FACE"], "enrollmentVersion": 1 }
]
}
413. Abaixo de ~175 KB por foto as 25 vagas são
utilizáveis — redimensione para ~640×640 antes de codificar.results, não o status HTTP. Um lote com 12 de 25 reprovadas
ainda responde 200.userExternalId repetido reprova o lote inteiro com 400.
Deduplique antes de enviar.const result = await client.users.enrollBatch({
dryRun: false,
enrollments: [
{ userExternalId: 'matricula-4471', image: img1, cpf: '12345678900' },
{ userExternalId: 'matricula-4472', image: img2, cpf: '98765432100' },
],
});
// Leia results, não o status HTTP: um lote meio reprovado ainda é 200.
for (const row of result.results) {
if (row.status === 'failed') console.error(row.userExternalId, row.error);
else if (row.referenceQuality === 'marginal') console.warn(row.userExternalId, row.warnings);
}
result = client.users.enroll_batch(
enrollments=[
{"userExternalId": "matricula-4471", "image": img1, "cpf": "12345678900"},
{"userExternalId": "matricula-4472", "image": img2, "cpf": "98765432100"},
],
dry_run=False,
)
# Leia results, não o status HTTP: um lote meio reprovado ainda é 200.
for row in result.results:
if row.status == "failed":
print(row.user_external_id, row.error)
elif row.reference_quality == "marginal":
print(row.user_external_id, row.warnings)
result, _ := client.Users.EnrollBatch(ctx, &signdocs.EnrollUsersBatchRequest{
Enrollments: []signdocs.BatchEnrollmentItem{
{UserExternalID: "matricula-4471", Image: img1, CPF: "12345678900"},
{UserExternalID: "matricula-4472", Image: img2, CPF: "98765432100"},
},
})
// Leia Results, não o status HTTP: um lote meio reprovado ainda é 200.
for _, row := range result.Results {
if row.Status == "failed" {
fmt.Println(row.UserExternalID, row.Error)
}
}
BatchEnrollmentModels.Request req = new BatchEnrollmentModels.Request();
req.setEnrollments(List.of(
new BatchEnrollmentModels.Item("matricula-4471", img1, "12345678900"),
new BatchEnrollmentModels.Item("matricula-4472", img2, "98765432100")));
req.setDryRun(false);
BatchEnrollmentModels.Response result = client.users().enrollBatch(req);
// Leia getResults(), não o status HTTP: um lote meio reprovado ainda é 200.
for (BatchEnrollmentModels.Result row : result.getResults()) {
if ("failed".equals(row.getStatus())) {
System.out.println(row.getUserExternalId() + ": " + row.getError());
}
}
$result = $client->users->enrollBatch([
['userExternalId' => 'matricula-4471', 'image' => $img1, 'cpf' => '12345678900'],
['userExternalId' => 'matricula-4472', 'image' => $img2, 'cpf' => '98765432100'],
], dryRun: false);
// Leia results, não o status HTTP: um lote meio reprovado ainda é 200.
foreach ($result->results as $row) {
if ($row->status === 'failed') {
echo $row->userExternalId . ': ' . $row->error . PHP_EOL;
}
}
var result = await client.Users.EnrollBatchAsync(new EnrollUsersBatchRequest(
Enrollments: new List<BatchEnrollmentItem>
{
new("matricula-4471", img1, "12345678900"),
new("matricula-4472", img2, "98765432100"),
},
DryRun: false));
// Leia Results, não o status HTTP: um lote meio reprovado ainda é 200.
foreach (var row in result?.Results ?? [])
{
if (row.Status == "failed") Console.WriteLine($"{row.UserExternalId}: {row.Error}");
}
| Código | Tipo | Causa | Solução |
|---|---|---|---|
400 |
Bad Request | Campo image ou cpf ausente/inválido |
Verifique se ambos os campos obrigatórios estão presentes e no formato correto |
422 |
Unprocessable Entity | Nenhum rosto detectado na imagem ou mais de um rosto | Envie uma imagem com exatamente um rosto visível e bem iluminado |
422 |
Feature Not Enabled | source: "DOCUMENT_PHOTO" sem documentExtractionEnabled |
Solicite a ativação da feature flag ao gerente de conta |
401 |
Unauthorized | Token inválido ou expirado | Obtenha um novo token via POST /oauth2/token |
DOCUMENT_PHOTO, certifique-se de que a foto no documento está nítida e sem reflexõesenrollmentHash retornado para rastreabilidade e auditoria