Zarv ID v3
O Zarv ID v3 é a nova versão da plataforma de análise de CPF e CNPJ da Zarv, com dados modulares vindos direto dos datasets, sem remapeamento. Ela tem dois produtos:
| Produto | O que entrega | Disponibilidade |
|---|---|---|
| Enriquecimento | Os dados de cada módulo do perfil (cadastro, finanças, processos, relacionamentos…) exatamente como o dataset os publica | Disponível |
| Verificação | A coleta do enriquecimento mais a decisão: regras eliminatórias, segmentação, score, análise por IA e aprovação automática | Em desenvolvimento |
Esta documentação cobre o enriquecimento. Nele não há regras de decisão, score nem análise por IA: é dado puro, organizado por módulo. Para uma decisão sobre o CPF ou CNPJ, use a verificação quando ela estiver disponível.
| Rota pública | O que faz |
|---|---|
POST /api/v3/enrichments | Cria um enriquecimento |
GET /api/v3/enrichments | Lista os enriquecimentos |
GET /api/v3/enrichments/{id} | Consulta um enriquecimento (e, opcionalmente, os dados) |
GET /api/v3/enrichments/{id}/modules/{module} | Consulta os dados de um módulo |
GET /api/v3/profiles | Lista os perfis do workspace |
GET /api/v3/profiles/{id} | Consulta um perfil e o dataset de cada módulo |
GET /api/v3/callback-secret | Consulta o segredo de assinatura do callback |
Todas as rotas ficam em https://services.zarv.com e usam token Bearer JWT. Veja Autenticação, o Webhook, a Referência da API e o formato de cada módulo em Datasets.
Antes de começar
Peça ao time da Zarv:
- as credenciais da API do seu workspace;
- os perfis que você vai usar, configurados para o seu workspace. O perfil define quais módulos rodam para o documento, e cada um atende uma entidade: você precisa de um perfil PF para consultar CPF e de um perfil PJ para consultar CNPJ.
Com os perfis configurados, GET /api/v3/profiles lista os que o seu workspace pode usar, com o profileId e o tipo (profile.kind) de cada um. Um enriquecimento só roda perfis enrichment: use GET /api/v3/profiles?kind=enrichment para ver só esses. GET /api/v3/profiles/{id} mostra o dataset de cada módulo, no mesmo formato de modules na resposta do enriquecimento, para você saber de antemão o formato dos dados (veja Datasets). Para mudar os módulos de um perfil, ou criar um novo, fale com o time da Zarv.
Como funciona
- Crie o enriquecimento com
POST /api/v3/enrichments, informandodocumenteprofileId(e, se quiser, umcallback). - A requisição espera até 25 segundos. Se o enriquecimento terminar nesse tempo, a resposta é
200com o corpo completo e os dados de todos os módulos emdata. - Se não terminar, a resposta é
202com{ "id", "status" }. A partir daí, escolha:- polling: consulte
GET /api/v3/enrichments/{id}respeitando o headerRetry-After; ou - callback: receba um
POSTassinado no seu endpoint quando o enriquecimento terminar (veja Webhook).
- polling: consulte
- Com o enriquecimento
COMPLETED, leia os dados com?include=dataou módulo a módulo.
O processamento continua mesmo que o seu cliente feche a conexão durante a espera.
Evite consultas repetidas
Envie o header Idempotency-Key em toda criação, com um valor único por consulta (um UUID, por exemplo). Ele é opcional, mas sem ele cada requisição cria um enriquecimento novo, cobrado de novo: um retry depois de um timeout de rede, ou um clique duplo, vira uma segunda consulta.
Com a chave, repetir a mesma requisição devolve o mesmo enriquecimento, sem nova consulta e sem nova cobrança, pelo tempo que for. Para uma consulta nova do mesmo documento, use uma chave nova. Os detalhes estão em Idempotência.
Exemplo
curl -X POST "https://services.zarv.com/api/v3/enrichments" \
-H "Authorization: Bearer SEU_TOKEN_DE_ACESSO" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2a9e-4b7d-4e2a-9c3f-0a1b2c3d4e5f" \
-d '{
"document": "111.444.777-35",
"profileId": "8f14e45f-ceea-4e7a-9c1a-2b3c4d5e6f70",
"callback": {
"url": "https://sua-empresa.com/webhooks/zarv",
"headers": { "Authorization": "Bearer <token-do-seu-endpoint>" }
},
"metadata": { "pedido": "12345" }
}'const res = await fetch("https://services.zarv.com/api/v3/enrichments", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
document: "111.444.777-35",
profileId: "8f14e45f-ceea-4e7a-9c1a-2b3c4d5e6f70",
metadata: { pedido: "12345" },
}),
});
if (res.status === 200) {
const enrichment = await res.json(); // terminou: dados em enrichment.data
} else if (res.status === 202) {
const { id } = await res.json(); // ainda rodando: polling ou callback
const retryAfter = Number(res.headers.get("Retry-After") ?? 5);
}Resposta 200 (resumida):
{
"id": "enr_0199a0b2-7c1e-7d4a-9f0e-3b1c2d4e5f60",
"status": "COMPLETED",
"outcome": "ok",
"entity": "person",
"document": "11144477735",
"profile": {
"id": "8f14e45f-ceea-4e7a-9c1a-2b3c4d5e6f70",
"name": "Cadastro PF",
"kind": "enrichment"
},
"modules": {
"profile": { "dataset": "person-profile-2.0", "status": "ok" },
"lawsuits": { "dataset": "person-lawsuits-2.0", "status": "ok" }
},
"metadata": { "pedido": "12345" },
"createdAt": "2026-09-28T13:00:00Z",
"completedAt": "2026-09-28T13:00:04Z",
"data": {
"profile": {
"dataset": "person-profile-2.0",
"module": "profile",
"profile": {
"exists": true,
"name": "MARIA DA SILVA",
"taxId": { "status": "REGULAR" }
},
"rules": [{ "rule": "PROFILE_PERSON_DECEASED", "fired": false }]
},
"lawsuits": {
"dataset": "person-lawsuits-2.0",
"module": "lawsuits",
"lawsuits": {},
"rules": []
}
}
}Resposta 202:
HTTP/1.1 202 Accepted
Retry-After: 5
Content-Type: application/json
{ "id": "enr_0199a0b2-7c1e-7d4a-9f0e-3b1c2d4e5f60", "status": "FETCHING" }Ciclo de vida
Status do enriquecimento
status | Significado | Terminal |
|---|---|---|
CREATED | Recebido; a coleta ainda não começou | Não |
FETCHING | Coletando os módulos do plano | Não |
COMPLETED | Todos os módulos do plano concluíram (ou o titular não existe) | Sim |
FAILED | A coleta não pôde ser concluída | Sim |
Um status terminal nunca muda.
Resultado (outcome)
Preenchido só quando COMPLETED; null nos demais status.
outcome | Significado |
|---|---|
ok | O titular foi encontrado e todos os módulos do plano rodaram. |
subject_not_found | O documento não tem titular conhecido. Só o módulo profile rodou; os demais não são consultados. |
O módulo profile roda em todo enriquecimento, primeiro: é ele que localiza o titular. Os demais módulos só rodam quando o titular existe.
Status de cada módulo
modules.{módulo} traz o estado de cada módulo do plano:
status | Significado |
|---|---|
pending | Ainda não começou |
retrying | Em nova tentativa após uma falha temporária (nextAttemptAt diz quando) |
ok | Concluído; os dados estão disponíveis |
failed | Falhou |
modules.{módulo}.dataset diz qual dataset respondeu (por exemplo, person-profile-2.0).
Falhas e novas rodadas
Falhas temporárias de um módulo são repetidas automaticamente. Quando a coleta de um módulo se esgota por um motivo não permanente, o enriquecimento não falha na hora: continua FETCHING e ganha até 3 novas rodadas de coleta, 15 minutos, 1 hora e 4 horas depois da falha. Cada rodada repete só os módulos que falharam. Enquanto aguarda uma rodada, o enriquecimento traz nextRetryAt (quando ela roda) e recoveryRound (quantas já rodaram). Se a coleta falhar de novo depois da 3ª rodada, o enriquecimento termina FAILED.
Uma falha permanente termina o enriquecimento FAILED direto. failureReason diz o motivo (por exemplo, retry_exhausted ou deadline_exceeded).
Prazo total (options.deadline). Para limitar quanto tempo o enriquecimento pode levar, envie options.deadline na criação, como duração (90s, 10m, 2h; mínimo 1m). Nenhuma nova tentativa ou rodada passa desse prazo: excedido, o enriquecimento termina FAILED com failureReason: deadline_exceeded, e a resposta passa a trazer deadline. Sem options.deadline, todas as tentativas e as 3 rodadas se aplicam.
Cobrança
Um enriquecimento COMPLETED é cobrado pela ação do perfil, mais um add-on por módulo add-on que concluiu. Com outcome: subject_not_found, só a ação do perfil é cobrada. Um enriquecimento FAILED não é cobrado, e um replay idempotente nunca cobra de novo.
Espera síncrona e polling
A criação espera até 25 segundos pelo fim do enriquecimento. Configure o timeout do seu cliente HTTP acima disso (35 segundos, por exemplo).
Toda resposta não terminal — o 202 da criação, um GET de um enriquecimento em andamento e um replay idempotente em andamento — traz Retry-After, em segundos:
- em geral,
5; - enquanto o enriquecimento aguarda uma nova rodada, os segundos até
nextRetryAt(no mínimo 5); - nunca além de um
deadlineainda à frente (então no mínimo 1).
Uma resposta terminal não traz Retry-After. Consulte GET /api/v3/enrichments/{id} depois do intervalo indicado; não há long polling.
async function waitForEnrichment(id, accessToken) {
for (;;) {
const res = await fetch(
`https://services.zarv.com/api/v3/enrichments/${id}`,
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
const body = await res.json();
const retryAfter = res.headers.get("Retry-After");
if (!retryAfter) return body; // COMPLETED ou FAILED
await new Promise((r) => setTimeout(r, Number(retryAfter) * 1000));
}
}Idempotência
Envie o header Idempotency-Key (até 255 caracteres; um UUID por pedido, por exemplo) na criação. Com ele, repetir a requisição é seguro — após um timeout de rede, por exemplo:
| Situação | Resposta |
|---|---|
| Mesma chave e mesmo corpo, enriquecimento terminado | 200 com o corpo do GET, sem nova cobrança |
| Mesma chave e mesmo corpo, ainda em andamento | 202 com { "id", "status" }; um callback enviado junto é rearmado |
| Mesma chave com corpo diferente | 422 idempotency_key_reused |
A chave não expira: ela sempre devolve o mesmo enriquecimento, que continua salvo. Para uma consulta nova do mesmo documento, use uma chave nova. options.deadline é normalizado antes da comparação: 2h e 120m são o mesmo pedido.
Dados por módulo
Os dados de cada módulo ficam em data.{módulo}, no formato publicado pelo dataset que respondeu — sem remapeamento. O formato de cada dataset, campo a campo, está em Datasets.
Para saber, antes de criar o enriquecimento, qual dataset cada módulo vai usar, consulte o perfil em GET /api/v3/profiles/{id}: o modules dele traz, por módulo, o mesmo dataset que aparece na resposta do enriquecimento (por exemplo, person-finance-1.0). Para ver os perfis do seu workspace, use GET /api/v3/profiles.
- Criação que termina a tempo (
200):datatraz todos os módulos. GET /api/v3/enrichments/{id}: seminclude, só o estado (modules), semdata.?include=datainclui todos os módulos concluídos;?include=profile,lawsuitsinclui só os módulos listados.
GET /api/v3/enrichments/{id}/modules/{module}: odata.{módulo}de um módulo, sozinho. Módulo ainda não concluído responde409 module_not_ready.GET /api/v3/enrichments: os itens são resumos, semmodulesnemdata.
Módulos e add-ons
O perfil (profileId) define quais módulos rodam e qual versão de dataset cada um usa. Veja os módulos de um perfil em GET /api/v3/profiles/{id}. Módulos add-on (como financeAdvanced) só rodam quando pedidos em options.modules e habilitados no perfil:
{
"document": "111.444.777-35",
"profileId": "8f14e45f-ceea-4e7a-9c1a-2b3c4d5e6f70",
"options": { "modules": { "financeAdvanced": {} } }
}Por enquanto, o valor de cada add-on é sempre {}. Um módulo que o perfil não tem, ou não permite, responde 422 module_not_in_profile.
Erros
Todo erro usa o mesmo envelope:
{
"error": {
"code": "profile_not_found",
"message": "profile not found"
}
}Trate o erro por error.code, que é estável; message é texto de diagnóstico e pode mudar. Nenhuma mensagem contém documento, token ou outro dado pessoal.
| HTTP | code | Quando |
|---|---|---|
400 | invalid_body | Corpo não é JSON válido ou tem campo desconhecido |
400 | invalid_document | CPF ou CNPJ inválido, na criação ou no filtro document da listagem |
400 | profile_required | profileId ausente |
400 | invalid_callback | callback inválido (URL não https, destino recusado, header reservado…) |
400 | invalid_deadline | options.deadline fora do formato ou abaixo de 1m |
400 | invalid_idempotency_key | Idempotency-Key com mais de 255 caracteres |
400 | invalid_cursor, invalid_limit | Paginação inválida na listagem |
400 | invalid_from, invalid_to, invalid_date_range | Filtro de data da listagem fora do RFC3339, ou from não anterior a to |
400 | invalid_entity | Filtro entity da listagem diferente de person, company, pf ou pj |
401 | — | Token ausente, inválido ou expirado |
402 | contract_required | O workspace não tem contrato ativo do produto ID |
402 | action_not_contracted | Há contrato ativo, mas ele não cobre todas as ações que o perfil (e os add-ons pedidos) exigem; a mensagem lista as que faltam |
403 | — | O token não tem permissão para esta rota |
404 | profile_not_found | Perfil inexistente, inativo, de outro workspace ou não disponível para o seu workspace |
404 | not_found | Enriquecimento não encontrado |
404 | module_not_found | Módulo fora do plano do enriquecimento |
409 | module_not_ready | Módulo ainda não concluído |
422 | module_not_in_profile | Add-on pedido que o perfil não tem ou não permite |
422 | profile_entity_mismatch | O perfil é de outra entidade (perfil PF com CNPJ, ou o contrário) |
422 | profile_kind_mismatch | O perfil não é do tipo enrichment (é um perfil de verificação); use um perfil enrichment |
422 | profile_misconfigured | O perfil está configurado de forma inválida; fale com o time da Zarv |
422 | profile_not_billable | O perfil ainda não é um produto precificado; fale com o time da Zarv |
422 | idempotency_key_reused | Mesma Idempotency-Key com corpo diferente |
429 | — | Limite de taxa excedido (veja Limites de uso) |
500 | internal_error | Falha inesperada |
503 | profiles_unavailable, billing_unavailable, idempotency_retry | Indisponibilidade temporária; repita com a mesma Idempotency-Key |
contract_required × action_not_contracted
Os dois são 402 e significam que a consulta não pode ser cobrada. contract_required diz que o workspace não tem nenhum contrato ativo do produto ID; action_not_contracted diz que o contrato existe, mas não inclui alguma ação necessária — tipicamente um add-on pedido em options.modules. Nenhum dos dois cria o enriquecimento nem cobra.