curl -X POST https://admin.kycert.com.br/api/v1/verificacoes/facial \
-H "x-api-key: $KYCERT_API_KEY" \
-F "checks=face_match,passive_liveness" \
-F "user_image=@/caminho/para/selfie.jpg" \
-F "ref_image=@/caminho/para/documento.jpg"const form = new FormData()
form.set('checks', 'face_match,passive_liveness')
form.set('user_image', userImageBlob, 'selfie.jpg')
form.set('ref_image', refImageBlob, 'documento.jpg')
const res = await fetch('https://admin.kycert.com.br/api/v1/verificacoes/facial', {
method: 'POST',
headers: { 'x-api-key': process.env.KYCERT_API_KEY },
body: form,
})
const { results } = await res.json()
console.log(results.face_match?.status, results.passive_liveness?.status)
import requests
url = "https://admin.kycert.com.br/api/v1/verificacoes/facial"
files = {
"user_image": ("example-file", open("example-file", "rb")),
"ref_image": ("example-file", open("example-file", "rb"))
}
payload = { "checks": "face_match,passive_liveness" }
headers = {"x-api-key": "<api-key>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('user_image', '<string>');
form.append('ref_image', '<string>');
form.append('checks', 'face_match,passive_liveness');
const options = {method: 'POST', headers: {'x-api-key': '<api-key>'}};
options.body = form;
fetch('https://admin.kycert.com.br/api/v1/verificacoes/facial', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://admin.kycert.com.br/api/v1/verificacoes/facial",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ref_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nface_match,passive_liveness\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data; boundary=---011000010111000001101001",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://admin.kycert.com.br/api/v1/verificacoes/facial"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ref_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nface_match,passive_liveness\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://admin.kycert.com.br/api/v1/verificacoes/facial")
.header("x-api-key", "<api-key>")
.header("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ref_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nface_match,passive_liveness\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://admin.kycert.com.br/api/v1/verificacoes/facial")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'multipart/form-data; boundary=---011000010111000001101001'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ref_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nface_match,passive_liveness\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"object": "facial_verification",
"results": {
"face_match": {
"status": "Approved",
"score": 92.4,
"warnings": [],
"request_id": "a1b2c3d4e5f67890"
},
"passive_liveness": {
"status": "Approved",
"score": 88.1,
"warnings": [],
"request_id": "b2c3d4e5f6a78901"
}
},
"livemode": true
}Verificação facial avulsa
Roda Face Match e/ou Passive Liveness sem vincular o resultado a
um cliente cadastrado — análoga a um bureau run avulso. Consome o mesmo
saldo do tenant dono da chave. customer_id nunca é atribuído por esta
rota; a persistência é só para reconciliação de billing/suporte.
Requer escopo biometria:write e o módulo “Verificação facial” ativo
para o tenant (ativação em duas camadas — módulo no master + config no
tenant — nenhuma acionável via API).
Síncrona: o resultado volta na própria resposta HTTP. Diferente do
fluxo automático de POST /customers/POST /customers/{id}/documents,
esta rota não dispara o webhook biometric_check.completed.
curl -X POST https://admin.kycert.com.br/api/v1/verificacoes/facial \
-H "x-api-key: $KYCERT_API_KEY" \
-F "checks=face_match,passive_liveness" \
-F "user_image=@/caminho/para/selfie.jpg" \
-F "ref_image=@/caminho/para/documento.jpg"const form = new FormData()
form.set('checks', 'face_match,passive_liveness')
form.set('user_image', userImageBlob, 'selfie.jpg')
form.set('ref_image', refImageBlob, 'documento.jpg')
const res = await fetch('https://admin.kycert.com.br/api/v1/verificacoes/facial', {
method: 'POST',
headers: { 'x-api-key': process.env.KYCERT_API_KEY },
body: form,
})
const { results } = await res.json()
console.log(results.face_match?.status, results.passive_liveness?.status)
import requests
url = "https://admin.kycert.com.br/api/v1/verificacoes/facial"
files = {
"user_image": ("example-file", open("example-file", "rb")),
"ref_image": ("example-file", open("example-file", "rb"))
}
payload = { "checks": "face_match,passive_liveness" }
headers = {"x-api-key": "<api-key>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('user_image', '<string>');
form.append('ref_image', '<string>');
form.append('checks', 'face_match,passive_liveness');
const options = {method: 'POST', headers: {'x-api-key': '<api-key>'}};
options.body = form;
fetch('https://admin.kycert.com.br/api/v1/verificacoes/facial', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://admin.kycert.com.br/api/v1/verificacoes/facial",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ref_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nface_match,passive_liveness\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data; boundary=---011000010111000001101001",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://admin.kycert.com.br/api/v1/verificacoes/facial"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ref_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nface_match,passive_liveness\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://admin.kycert.com.br/api/v1/verificacoes/facial")
.header("x-api-key", "<api-key>")
.header("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ref_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nface_match,passive_liveness\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://admin.kycert.com.br/api/v1/verificacoes/facial")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'multipart/form-data; boundary=---011000010111000001101001'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"user_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ref_image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nface_match,passive_liveness\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"object": "facial_verification",
"results": {
"face_match": {
"status": "Approved",
"score": 92.4,
"warnings": [],
"request_id": "a1b2c3d4e5f67890"
},
"passive_liveness": {
"status": "Approved",
"score": 88.1,
"warnings": [],
"request_id": "b2c3d4e5f6a78901"
}
},
"livemode": true
}Quando usar
Use este endpoint para rodar Face Match e/ou Passive Liveness sem vincular o resultado a um cliente cadastrado — útil para validar biometria antes de decidir se vale a pena criar o cadastro, ou para fluxos que não passam por POST /customers/POST /customers//documents (o par selfie+identidade não dispara a verificação automática — ver Verificação Facial para os dois fluxos lado a lado). Requer escopobiometria:write e o módulo “Verificação facial” ativo para o tenant — ativação em duas camadas (módulo no master + configuração no tenant), nenhuma acionável via esta ou qualquer outra chamada de API.
Corpo da requisição
O corpo émultipart/form-data.
| Campo | Obrigatório | Descrição |
|---|---|---|
user_image | Sim | Imagem de referência do rosto do titular |
ref_image | Somente se checks incluir face_match | Segunda imagem, para comparação com user_image |
checks | Não | "face_match", "passive_liveness" ou "face_match,passive_liveness". Default: face_match se ref_image foi enviado, senão passive_liveness |
400 image_too_large. Formato fora da lista retorna 400 invalid_image_format.
Checks disponíveis
| Check | O que verifica | Exige ref_image? |
|---|---|---|
face_match | Se o rosto em user_image é a mesma pessoa de ref_image | Sim |
passive_liveness | Se user_image é de uma pessoa real ao vivo (anti-spoofing) — sem exigir gestos ou vídeo | Não |
face_match sem ref_image retorna 400 missing_ref_image.
Exemplo
curl -X POST https://admin.kycert.com.br/api/v1/verificacoes/facial \
-H "x-api-key: $KYCERT_API_KEY" \
-F "checks=face_match,passive_liveness" \
-F "user_image=@/caminho/para/selfie.jpg" \
-F "ref_image=@/caminho/para/documento.jpg"
{
"object": "facial_verification",
"results": {
"face_match": {
"status": "Approved",
"score": 92.4,
"warnings": [],
"request_id": "a1b2c3d4e5f67890"
},
"passive_liveness": {
"status": "Approved",
"score": 88.1,
"warnings": [],
"request_id": "b2c3d4e5f6a78901"
}
},
"livemode": true
}
Resposta parcial
A resposta nunca é tudo-ou-nada. Se os dois checks forem pedidos e só um falhar no provedor, o resultado do que teve sucesso (já cobrado) volta normalmente — só aquela entrada específica deresults vira {status: "Error", message: "..."}:
{
"object": "facial_verification",
"results": {
"face_match": {
"status": "Approved",
"score": 92.4,
"warnings": [],
"request_id": "a1b2c3d4e5f67890"
},
"passive_liveness": {
"status": "Error",
"message": "Falha ao processar este check com o provedor."
}
},
"livemode": true
}
502 biometric_check_failed quando nenhum dos checks pedidos teve sucesso.
O que este endpoint não faz
- Não vincula o resultado a um cliente —
customer_idé semprenullna persistência interna; esta rota nunca cria nem atualiza umcustomer. Para o fluxo que vincula ao cadastro, veja Verificação Facial. - Não tem
Idempotency-Key— reenviar a mesma chamada roda (e cobra) a verificação de novo. - Não dispara o webhook
biometric_check.completed— a chamada é síncrona e o resultado já volta na própria resposta HTTP. Assinar esse webhook não traz nenhum evento para verificações feitas por este endpoint (o evento cobre apenas o fluxo automático dePOST /customers/POST /customers/{id}/documents— ver Webhooks).
Erros
Formato completo do envelope e estratégia de retry em Tratamento de erros. Os códigos deste endpoint:missing_api_key, invalid_api_key, insufficient_scope, channel_disabled, module_disabled, invalid_multipart, missing_user_image, invalid_checks, missing_ref_image, invalid_image_format, image_too_large, billing_suspended, internal_error, biometric_check_failed.Authorizations
API key no header x-api-key (recomendado)
Headers
"2026-06-03"
Body
Imagem de referência do rosto do titular (JPG, PNG, WEBP ou TIFF, até 5MB)
Segunda imagem para comparação — obrigatória quando checks inclui
face_match (mesmas regras de formato/tamanho de user_image).
"face_match", "passive_liveness" ou "face_match,passive_liveness".
Default: face_match se ref_image foi enviado, senão passive_liveness.
"face_match,passive_liveness"
Response
Verificação processada. A resposta nunca é "tudo ou nada" — quando
os dois checks são pedidos e só um falha no provedor, o outro
(já rodado e já cobrado) é retornado normalmente; só escala para
502 quando NENHUM check pedido teve sucesso.
facial_verification Chaveado pelos checks pedidos (face_match, passive_liveness).
Cada entrada é {status: Approved|Declined, score, warnings, request_id}
ou, se aquele check específico falhou no provedor, {status: Error, message}.
Show child attributes
Show child attributes