CPFHub.io
Start for free

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:

JSON
{
  "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.

JSON
{
  "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

StatusMessageDescription
200 OKNoneSuccessful lookup. The data field has the results.
202 AcceptedNoneBatch lookup only: batch accepted for processing.

4xx: Client errors

StatusMessageDescription
400 Bad RequestCPF 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 RequestData 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 RequestCampo '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 RequestLimite de 10000 CPFs por lote excedido. (limit of 10,000 CPFs per batch exceeded)Batch lookup only: more than 10,000 CPFs.
401 UnauthorizedAPI Key não fornecida (API key not provided)The x-api-key header is missing. error is a string.
401 UnauthorizedAPI Key inválida (invalid API key)The API key does not exist. error is a string.
401 UnauthorizedAPI Key inativa (inactive API key)The API key was replaced by a new one in the dashboard. error is a string.
403 ForbiddenLimite 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 ForbiddenCré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 ForbiddenUsuário inativo. Entre em contato com o suporte. (inactive user, contact support)Account deactivated. error is a string.
403 ForbiddenConta bloqueada. Entre em contato com o suporte. (account blocked, contact support)Account blocked. error is a string.
404 Not FoundCPF não encontrado na base de dados (CPF not found in the database)Simple Lookup only. Does not use a credit.
404 Not FoundLote 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 EntityCPF inválido. Dígito verificador incorreto. (incorrect check digit)The CPF failed the mathematical check of its check digits.
422 Unprocessable EntityNenhum 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 EntityMessage from Receita FederalReal-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 RequestsLimite 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

StatusMessageDescription
500 Internal Server ErrorErro ao validar assinatura (error validating subscription)Internal failure while reading your plan. error is a string.
500 Internal Server ErrorBanco de dados indisponível (database unavailable)Internal failure on the Simple Lookup.
500 Internal Server ErrorFalha ao criar lote. (failed to create the batch)Batch lookup only: the batch could not be created.
502 Bad GatewayFalha ao interpretar a resposta da Receita Federal. (failed to parse the Receita Federal response)Real-Time Lookup only. Does not use a credit.
502 / 503A 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 / 503A 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 UnavailableNã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 UnavailableFalha ao registrar validação (failed to record the lookup)Internal failure while saving the lookup. Does not use a credit.
503 Service Unavailableusage_unavailableThe 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:

JSON
{
  "success": false,
  "error": "usage_unavailable",
  "message": "Não foi possível verificar o saldo de créditos. Tente novamente."
}

Error handling example

TypeScript
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}`)
  }
}
Python
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

StatusRetry?How
429YesWait for the value of the Retry-After header
500, 502, 503 on a GET (Simple Lookup, batch status, balance)Yes, up to 3 timesBackoff: 1 s, 2 s, 4 s
502, 503 received on POST /cpf/realtimeYes, up to 3 timesBackoff: 2 s, 4 s, 8 s
400, 401, 403, 404, 422NoFix 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:

TestRequestResponse
CPF with fewer than 11 digitsGET /cpf/123400
Incorrect check digitGET /cpf/11111111111422
Invalid date of birthPOST /cpf/realtime with {"cpf":"12345678909","birthDate":"1990-06"}400
No API keyany route, without the x-api-key header401
Account balanceGET /quota200, always free
  • A 200 response uses credits. The Free plan has 50 credits to test the success path.
  • The CPF 12345678909 in the examples is fictional. To test success, use a CPF whose holder authorized the lookup.

Updated on October 4, 2026