Error Codes
The API follows standard REST conventions for HTTP status codes. Use the HTTP status to decide what to do and the message only for logging or display.
Error structure
There are two formats. Most errors return error as an object with message:
{
"success": false,
"data": null,
"error": {
"message": "CPF não encontrado na base de dados"
}
}Authentication and account errors return error as a string: 401, 403 for an inactive or blocked account, 403 "Limite de créditos excedido" (credit limit exceeded) on a batch submission with a zero balance, 500 "Erro ao validar assinatura" (error validating subscription) and 503 "usage_unavailable". The credit 403 on the Simple Lookup and the Real-Time Lookup comes as an object.
{
"success": false,
"error": "API Key não fornecida"
}To read the message in both cases: typeof body.error === 'string' ? body.error : body.error?.message.
Use the HTTP status
Decide how to handle an error by its HTTP status. The messages are in Portuguese and are meant for logging and display, so do not compare the text in your code. The translations in parentheses below are for reference only.
Full reference
2xx: Success
| Status | Message | Description |
|---|---|---|
200 OK | None | Successful lookup. The data field has the results. |
202 Accepted | None | Batch lookup only: batch accepted for processing. |
4xx: Client errors
| Status | Message | Description |
|---|---|---|
400 Bad Request | CPF inválido. Deve conter 11 dígitos. (the CPF must have 11 digits) | The CPF does not have 11 digits after the formatting is removed. |
400 Bad Request | Data de nascimento inválida. Use o formato DD/MM/YYYY ou DDMMYYYY (incorrect date of birth, use the format DD/MM/YYYY or DDMMYYYY) | Real-Time Lookup only: birthDate missing or invalid. |
400 Bad Request | Campo 'cpfs' deve ser um array não vazio. (the cpfs field must be a non-empty array) | Batch lookup only: cpfs missing, empty or not an array. |
400 Bad Request | Limite de 10000 CPFs por lote excedido. (limit of 10,000 CPFs per batch exceeded) | Batch lookup only: more than 10,000 CPFs. |
401 Unauthorized | API Key não fornecida (API key not provided) | The x-api-key header is missing. error is a string. |
401 Unauthorized | API Key inválida (invalid API key) | The API key does not exist. error is a string. |
401 Unauthorized | API Key inativa (inactive API key) | The API key was replaced by a new one in the dashboard. error is a string. |
403 Forbidden | Limite de créditos excedido (credit limit exceeded) | Balance smaller than the lookup cost, on plans without overage. On the Simple Lookup and the Real-Time Lookup, error is an object. On a batch submission with a balance too low for even one lookup, error is a string. |
403 Forbidden | Créditos insuficientes para o lote: N necessários. (not enough credits for the batch: N needed) | Batch lookup only: the balance does not cover all valid CPFs in the list, on plans without overage. |
403 Forbidden | Usuário inativo. Entre em contato com o suporte. (inactive user, contact support) | Account deactivated. error is a string. |
403 Forbidden | Conta bloqueada. Entre em contato com o suporte. (account blocked, contact support) | Account blocked. error is a string. |
404 Not Found | CPF não encontrado na base de dados (CPF not found in the database) | Simple Lookup only. Does not use a credit. |
404 Not Found | Lote não encontrado. (batch not found) | Batch status only: the jobId does not exist or was created with another API key. In rare cases of an internal failure it also appears, so retry once before treating the batch as nonexistent. |
422 Unprocessable Entity | CPF inválido. Dígito verificador incorreto. (incorrect check digit) | The CPF failed the mathematical check of its check digits. |
422 Unprocessable Entity | Nenhum CPF válido na lista enviada. (no valid CPF in the submitted list) | Batch lookup only: no item in the list is a valid CPF. |
422 Unprocessable Entity | Message from Receita Federal | Real-Time Lookup only: Receita Federal refused the lookup, for example because the date of birth does not match. Does not use a credit. |
429 Too Many Requests | Limite de requisições por minuto excedido. Tente novamente em N segundos. (per-minute request limit exceeded, try again in N seconds) | You went over the per-minute limit for the Simple Lookup, the Real-Time Lookup or the batch submission. It includes the Retry-After header. Does not use a credit. |
A route that does not exist responds 404 with the plain text NOT_FOUND, without JSON. Check the Content-Type before reading the body as JSON.
5xx: Server errors
| Status | Message | Description |
|---|---|---|
500 Internal Server Error | Erro ao validar assinatura (error validating subscription) | Internal failure while reading your plan. error is a string. |
500 Internal Server Error | Banco de dados indisponível (database unavailable) | Internal failure on the Simple Lookup. |
500 Internal Server Error | Falha ao criar lote. (failed to create the batch) | Batch lookup only: the batch could not be created. |
502 Bad Gateway | Falha ao interpretar a resposta da Receita Federal. (failed to parse the Receita Federal response) | Real-Time Lookup only. Does not use a credit. |
502 / 503 | A Receita Federal não respondeu a tempo. Tente novamente em alguns segundos. Nenhum crédito foi cobrado. (Receita Federal did not respond in time, try again in a few seconds, no credit was charged) | Real-Time Lookup only. Does not use a credit. |
502 / 503 | A Receita Federal está com alta demanda agora. Tente novamente em alguns segundos. Nenhum crédito foi cobrado. (Receita Federal is under high demand, try again in a few seconds, no credit was charged) | Real-Time Lookup only. Does not use a credit. |
503 Service Unavailable | Não foi possível reservar os créditos. Tente novamente. (could not reserve the credits, try again) | Internal failure while reserving the credits for the lookup. Does not use a credit. |
503 Service Unavailable | Falha ao registrar validação (failed to record the lookup) | Internal failure while saving the lookup. Does not use a credit. |
503 Service Unavailable | usage_unavailable | The credit balance could not be checked. Does not use a credit. |
The 503 for an unavailable balance returns error as a string and a message field:
{
"success": false,
"error": "usage_unavailable",
"message": "Não foi possível verificar o saldo de créditos. Tente novamente."
}Error handling example
const res = await fetch(`https://api.cpfhub.io/cpf/${cpf}`, {
headers: { 'x-api-key': process.env.CPFHUB_API_KEY! },
signal: AbortSignal.timeout(10_000),
})
const isJson = res.headers.get('content-type')?.includes('application/json')
const body = isJson ? await res.json() : null
if (res.ok) {
console.log(body.data.name)
} else {
// error is a string in some errors and an object { message } in the rest
const message = typeof body?.error === 'string' ? body.error : body?.error?.message
switch (res.status) {
case 404:
// CPF not found: no credit used
break
case 400:
case 422:
// Bad input: fix it, do not retry
break
case 401:
case 403:
// Bad key, no credits left or blocked account: do not retry
throw new Error(message)
case 429:
// Per-minute limit: wait Retry-After seconds and retry
await new Promise(r => setTimeout(r, Number(res.headers.get('retry-after') ?? 5) * 1000))
break
default:
// 5xx: retry with backoff (see the table below)
throw new Error(message ?? `HTTP ${res.status}`)
}
}import os, time, requests
res = requests.get(
f"https://api.cpfhub.io/cpf/{cpf}",
headers={"x-api-key": os.environ["CPFHUB_API_KEY"]},
timeout=10,
)
is_json = "application/json" in res.headers.get("content-type", "")
body = res.json() if is_json else {}
if res.ok:
print(body["data"]["name"])
else:
error = body.get("error")
message = error if isinstance(error, str) else (error or {}).get("message")
if res.status_code == 404:
print("CPF not found")
elif res.status_code == 429:
time.sleep(int(res.headers.get("retry-after", "5"))) # then retry
elif res.status_code >= 500:
pass # retry with backoff
else:
raise RuntimeError(message)Retries
| Status | Retry? | How |
|---|---|---|
429 | Yes | Wait for the value of the Retry-After header |
500, 502, 503 on a GET (Simple Lookup, batch status, balance) | Yes, up to 3 times | Backoff: 1 s, 2 s, 4 s |
502, 503 received on POST /cpf/realtime | Yes, up to 3 times | Backoff: 2 s, 4 s, 8 s |
400, 401, 403, 404, 422 | No | Fix the input, the key or the balance |
POST is not idempotent
POST /cpf/realtime and POST /cpf/bulk do not accept an idempotency key. If the connection drops or your timeout fires before the response, the lookup may have completed and been charged, or the batch may have been created. Before retrying, check your balance with GET /quota and, for a batch, the recent submissions in the dashboard. Retry without checking only when you received 429, 502 or 503.
Testing without spending credits
These do not use credits: 400, 401, 404, 422 and 429. You can test your error handling for free:
| Test | Request | Response |
|---|---|---|
| CPF with fewer than 11 digits | GET /cpf/123 | 400 |
| Incorrect check digit | GET /cpf/11111111111 | 422 |
| Invalid date of birth | POST /cpf/realtime with {"cpf":"12345678909","birthDate":"1990-06"} | 400 |
| No API key | any route, without the x-api-key header | 401 |
| Account balance | GET /quota | 200, always free |
- A
200response uses credits. The Free plan has 50 credits to test the success path. - The CPF
12345678909in the examples is fictional. To test success, use a CPF whose holder authorized the lookup.
Updated on October 4, 2026