Autenticação
A Veridia usa autenticação por bearer token:
Authorization: Bearer qv_pub_FJJWXMA2RN2XPRDK6YJX4KTVD0XSQHW9
Dois endpoints são a exceção: PUT /v1/verify/upload/... e GET /v1/verify/challenge/.../next não aceitam chave de API alguma. Eles se autenticam com o token de upload de vida curta que o /init entrega. Veja Enviando as imagens.
Famílias de chaves
| Família | Usar a partir de | Pode fazer | Não pode fazer |
|---|---|---|---|
| Publicável | Navegador, widget, app mobile | POST /v1/verify/init, POST /v1/verify/submit | Ler veredictos |
| Secreta | Apenas no seu servidor | Tudo o que uma chave publicável faz, mais GET /v1/verify/:id | — |
A chave publicável é segura para embarcar na sua página. Ela foi projetada para isso. Ela pode iniciar verificações contra o seu saldo e pode submetê-las — mas nunca pode ler um resultado.
A chave secreta nunca é segura para embarcar. Ela pode obter o veredicto, os campos de identidade extraídos e os scores de qualquer verificação do seu tenant. Trate-a como a senha de um banco de dados.
Por que a separação existe
Uma chave publicável é, por definição, legível por qualquer pessoa que abra o código-fonte da sua página. Se essa chave pudesse chamar GET /v1/verify/:id, então todos os resultados de KYC dos seus clientes — veredicto, confiança, número do documento, data de nascimento — estariam a um fetch de distância de qualquer pessoa com as ferramentas de desenvolvedor abertas.
Por isso o endpoint de resultados recusa chaves publicáveis com um código de erro dedicado, secret_key_required, em vez de uma falha genérica de autenticação. O código existe justamente para que você não saia caçando um erro de digitação em uma chave que é perfeitamente válida.
Ambientes
Todo tenant recebe dois conjuntos paralelos de chaves. Repare bem nos prefixos de teste — eles são qv_pubt_ e qv_sect_, com o t antes do underscore. Não qv_pub_test_.
| Ambiente | Publicável | Secreta |
|---|---|---|
| Teste | qv_pubt_... | qv_sect_... |
| Produção | qv_pub_... | qv_sec_... |
Uma chave de teste percorre todo o fio, nada do trabalho. Cada request faz a mesma jornada que produção — autenticação de upload real, uma linha de verificação real, um webhook real assinado com seu segredo real e retentado na mesma escada, legível com sua chave secreta de teste — mas nenhum OCR roda, nenhum pipeline de ML roda, nenhum crédito é gasto, e o veredicto é escolhido por você, deterministicamente:
userRef contém | Veredicto | Evento do webhook |
|---|---|---|
+reject | rejected | verification.rejected |
+review | review | verification.review |
| qualquer outra coisa | approved | verification.approved |
{ "userRef": "qa-user-17+reject", "documentType": "passport", "country": "PY" }
Resultados determinísticos significam que seu CI pode assertar sobre cada ramo do webhook em vez de torcer para o pipeline opinar igual duas vezes. As imagens enviadas ainda precisam ser JPEGs reais dentro dos limites de tamanho — o caminho dos bytes é exercitado de propósito — mas o conteúdo é ignorado: os campos extraídos voltam como marcadores inconfundíveis (TEST PERSONA, TEST-000000).
Chaves de teste e de produção entregam na mesma URL de webhook do tenant. Por isso todo envelope de evento carrega um campo env — "test" ou "live" — e seu handler precisa ramificar sobre ele. Um verification.approved sintético ativando uma conta real é exatamente o acidente que esse campo existe para prevenir.
Fluxo de trabalho recomendado: desenvolva com chaves de teste, asserte os três resultados no CI, e então troque para chaves de produção. Nunca use uma chave de produção em staging.
Lista de domínios permitidos (chaves publicáveis)
Uma chave publicável pode carregar uma lista de origens permitidas. Quando essa lista não está vazia, uma requisição de navegador cujo Origin não esteja nela é rejeitada com origin_not_allowed (403).
As entradas são comparadas com o hostname puro — sem esquema, sem porta:
yourapp.com
staging.yourapp.com
localhost
http://localhost:3000 não é uma entrada válida. Ela nunca vai casar com nada, porque a checagem compara com o hostname localhost.
Curingas funcionam: *.yourapp.com casa com app.yourapp.com mas deliberadamente não casa com yourapp.com em si. Liste os dois se precisar dos dois.
A garantia funciona ao contrário do que a maioria das pessoas espera.
- Toda chave é criada com uma lista vazia, e uma lista vazia permite todas as origens. A checagem é pulada inteiramente quando a lista está vazia. É opt-in, não opt-out. O painel não expõe hoje um campo para preenchê-la.
- Ela só se aplica a requisições de navegador. curl, um servidor, um script ou qualquer um dos nossos SDKs server-side não envia cabeçalho
Origin, então não há nada a comparar e a requisição segue. Ela nunca consegue restringir uso fora do navegador. - Adicionar a sua primeira entrada vira a chave de "qualquer origem" para "somente esta lista". Se você adicionar
https://yourapp.com— com o esquema, que não vai casar — você vai de tudo permitido para tudo bloqueado em um único passo. Adicione o hostname puro.
O que essa lista de fato faz é delimitar onde o seu widget pode rodar. Ela não é um controle de acesso. O que protege uma chave publicável é o fato de ela não poder ler resultados, somados aos seus limites de taxa e ao seu saldo de créditos.
Trate uma chave publicável como pública, porque ela é.
Usando a chave secreta
Somente do lado do servidor. Todos os exemplos abaixo obtêm um veredicto.
curl
curl -X GET https://api.xxuxe.online/v1/verify/vf_AG07CDWRRFQV4T05ZXG2 \
-H "Authorization: Bearer qv_sec_YOUR_SECRET"
Em modo de teste a chave começa com qv_sect_.
JavaScript / Node.js
const response = await fetch(`https://api.xxuxe.online/v1/verify/${id}`, {
headers: {
'Authorization': `Bearer ${process.env.VERIDIA_SECRET_KEY}`,
},
});
const data = await response.json();
Python
import os, requests
response = requests.get(
f"https://api.xxuxe.online/v1/verify/{verification_id}",
headers={"Authorization": f"Bearer {os.environ['VERIDIA_SECRET_KEY']}"},
)
data = response.json()
PHP
<?php
$ch = curl_init("https://api.xxuxe.online/v1/verify/$verificationId");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . $_ENV['VERIDIA_SECRET_KEY'],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
Substituindo uma chave
O painel suporta duas operações: criar e revogar. Não há rotação, e não há período de carência.
Revogar uma chave a apaga do edge store na hora. Toda requisição em andamento que a use começa a falhar com invalid_api_key no mesmo instante — não existe janela de sobreposição em que a chave antiga continue funcionando.
Então a ordem segura é: crie a chave nova primeiro, faça o deploy dela em todos os lugares, confirme que o tráfego está passando por ela, e só então revogue a antiga. Fazer na ordem inversa é uma indisponibilidade.
Se você suspeita de um vazamento, é exatamente aí que você quer o corte imediato — revogue primeiro e aceite a interrupção. Só faça isso com consciência.
Boas práticas
- Nunca commite chaves secretas. Variáveis de ambiente ou um gerenciador de segredos.
- Chaves separadas por ambiente. Nunca uma chave de produção em staging.
- Criar-e-depois-revogar ao substituir uma chave, pelo motivo acima.
- Registre o
requestIddas respostas em log. Isso transforma um chamado de suporte vago em um rastreável. - Para qualquer chamada server-side, use a chave secreta. A chave publicável é para clientes.
Erros de autenticação
| HTTP | Código de erro | O que significa |
|---|---|---|
401 | missing_api_key | Sem cabeçalho Authorization, ou não é o esquema Bearer |
401 | invalid_api_key | A chave não existe, foi revogada, ou o prefixo/formato não é parseável |
401 | secret_key_required | Uma chave publicável foi usada em GET /v1/verify/:id |
402 | insufficient_credits | O saldo de créditos do tenant está zerado |
403 | origin_not_allowed | Requisição de navegador vinda de uma origem fora de uma lista permitida não vazia |
429 | rate_limited | Veja Limites de taxa — pode ser a camada por IP, verificada antes da sua chave |
503 | backend_unavailable | Pipeline inacessível — transitório, tente de novo com backoff |
Repare que insufficient_credits é 402, não 403. Ele também é levantado durante a autenticação, o que significa que pode aparecer no /init antes de você ter feito qualquer outra coisa — uma causa comum de "o widget só diz que algo deu errado" em uma conta de teste esgotada.
Veja Erros para o catálogo completo.
O que vem a seguir
- POST /v1/verify/init — inicia uma verificação
- Erros — referência completa de códigos de erro
- Limites de taxa — os dois limites, e por que 429 pode vir antes da autenticação