CNPJ Batch Lookup
Send a list of up to 10,000 CNPJs and get, for each one, the same record as the CNPJ Lookup, processed in the background. It costs 1 credit per CNPJ found.
Using Cursor, Lovable, v0 or another AI? Copy the CNPJ batch lookup prompt and paste it into your assistant.
Without code, the dashboard accepts a list of CNPJs and shows the result for each one.
Two-step flow:
POST /cnpj/bulksends the list and responds right away with ajobId.GET /cnpj/bulk/{jobId}returns progress and results. Repeat untilstatusis"done"or"failed".
Endpoint
https:/ / api.cpfhub.io/ cnpj/ bulkParameters
| Where | Name | Required | Description |
|---|---|---|---|
| Header | x-api-key | Yes | Your API key. See Authentication. |
| Header | Content-Type | Yes | application/json |
| Body | cnpjs | Yes | string[] of CNPJs, with or without formatting, numeric or alphanumeric. Not empty, at most 10,000. Items that are not strings count as invalid. |
| Body | fileName | No | Free label. Comes back as file_name in the status. |
| Body | format | No | Free label for the source format (e.g. "csv"). Comes back as format. |
What happens on submission
| Step | Effect |
|---|---|
| Normalization | Removes dots, slash and hyphen and converts letters to uppercase. CNPJs with a wrong format or check digit go to invalid and are not charged. |
| Duplicates | Removed. Each CNPJ is looked up and charged once. They count in neither totalRecords nor invalid. |
| Balance | Without overage, the balance must cover all valid CNPJs (totalRecords × lookup cost, 1 credit by default). If it does not, 403 and nothing is charged. |
| Pending batches | There is a limit of batches being processed at the same time per customer. Above it, 429. |
More than 10,000 CNPJs: split them into chunks and send one POST for each, waiting for earlier batches if you get a 429.
Example
Check the status before reading data: on error, the response has no jobId.
curl -X POST "https://api.cpfhub.io/cnpj/bulk" \
-H "x-api-key: $CPFHUB_API_KEY" \
-H "content-type: application/json" \
-d '{
"cnpjs": ["11222333000181", "12.ABC.345/01DE-35"]
}'Response
HTTP 202 Accepted
{
"success": true,
"data": {
"jobId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "processing",
"totalRecords": 2,
"invalid": 0
}
}| Field | Type | Description |
|---|---|---|
data.jobId | string | Batch ID. Keep it to check the status. |
data.status | string | Always "processing" |
data.totalRecords | number | Valid, unique CNPJs that will be looked up |
data.invalid | number | Discarded items (format, check digit or not a string) |
The submission responds in camelCase. The status responds in snake_case.
Status and results
https:/ / api.cpfhub.io/ cnpj/ bulk/ {jobId}Use the same API key as the submission. It uses no credit and works with a zero balance.
Polling:
- Start every 5 s and increase up to 30 s for large batches. Each call returns the whole
cnpjsarray, and each CNPJ record is much larger than a CPF one. - Set a maximum wait in the client (e.g. 30 min). Batch time depends on how many CNPJs are in the submission.
- On
5xxor a network error, try again on the next cycle. TheGETis safe to repeat.
curl "https://api.cpfhub.io/cnpj/bulk/7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-H "x-api-key: $CPFHUB_API_KEY"HTTP 200 OK
{
"success": true,
"data": {
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "done",
"file_name": null,
"format": null,
"total_records": 2,
"processed": 2,
"found": 1,
"not_found": 1,
"skipped": 0,
"error": null,
"created_at": "2026-10-15T14:15:16.000Z",
"updated_at": "2026-10-15T14:15:18.000Z",
"cnpjs": [
{
"cnpj": "11222333000181",
"found": true,
"data": {
"cnpj": "11222333000181",
"razao_social": "EMPRESA EXEMPLO LTDA",
"situacao_cadastral": "Ativa",
"...": "other CNPJ Lookup fields"
}
},
{
"cnpj": "12ABC34501DE35",
"found": false,
"data": null
}
]
}
}| Field | Description |
|---|---|
data.id | Batch ID (the jobId from the submission) |
data.status | "processing", "done" or "failed" |
data.total_records | Valid, unique CNPJs in the batch |
data.processed | CNPJs already processed (includes skipped) |
data.found | Found. Each uses 1 credit by default (your plan's cost is in the dashboard) |
data.not_found | Not found. No credit used. |
data.skipped | Not looked up because the balance ran out during the batch (no overage). No credit used. |
data.error | Failure reason when status is "failed" |
data.file_name, data.format | Labels sent in the POST |
data.created_at, data.updated_at | ISO 8601 dates |
data.cnpjs[] | Result for each processed CNPJ: cnpj (14 characters without formatting), found and data, which is the same record as the CNPJ Lookup or null when found is false |
While "processing", the counters update at intervals and may lag behind cnpjs.
Errors
The API only responds in Portuguese. Error messages are shown as returned.
| Status | When it happens | What to do |
|---|---|---|
400 | cnpjs missing, empty, not an array ("Campo 'cnpjs' deve ser um array não vazio.") or more than 10,000 items ("Limite de 10000 CNPJs por lote excedido.") | Fix the body. |
401 | Missing, invalid or inactive API key (error is a string) | Check the x-api-key header. |
403 | Without overage: balance lower than the batch (error.message: "Créditos insuficientes para o lote: N necessários.", insufficient credits). Also zero balance and inactive/blocked account. POST only. | Add credits or shrink the batch. |
404 | jobId does not exist or was created with another API key ("Lote não encontrado.") | Check the jobId and the key. |
422 | No valid CNPJ in the list ("Nenhum CNPJ válido na lista enviada.") | Fix the list. |
429 | Per-minute limit exceeded, or pending batch limit ("Limite de N lotes pendentes por cliente excedido."). POST only. | Wait the seconds in the Retry-After header (60 s for the pending batch limit). Let earlier batches finish before sending more. |
500 | "Falha ao criar lote." (batch creation failed). No batch was created. | Try again. Report it if it persists. |
Error format and retries: Error Codes. Per-minute limit and credits: Usage Limits.
Details
Batch states
| Situation | How to tell | What to do |
|---|---|---|
| Processing | status: "processing" | Keep polling. cnpjs already has partial results. |
| Done | "done" and skipped: 0 | Use cnpjs. |
| Done with skipped | "done" and skipped > 0 | The balance ran out during the batch. Add credits and resend only the missing CNPJs. |
| Failed | "failed" | Read error. The found items in cnpjs were already charged. Resend only the missing ones. |
With overage, no CNPJ is skipped: all are looked up and the excess is charged.
Matching results to your list
cnpjs does not follow the submission order and does not include skipped or unprocessed items. Normalize your list (no dots, slash or hyphen, uppercase) and match on the cnpj field. The missing ones are the ones to resend (including invalid ones, which cost nothing).
const normalize = (cnpj: string) => cnpj.replace(/[./-]/g, '').toUpperCase()
const results = new Map(job.cnpjs.map((item) => [item.cnpj, item]))
for (const original of myCnpjs) {
const result = results.get(normalize(original))
console.log(original, result ? (result.found ? result.data.razao_social : 'not found') : 'no result')
}
// CNPJs to resend in a new batch (if the batch failed or had skipped items)
const missing = [...new Set(myCnpjs.map(normalize))].filter((cnpj) => !results.has(cnpj))More
- Submission is not idempotent: each accepted
POSTcreates a new batch and charges the found items. If the connection drops without ajobId, check the dashboard orGET /quotabefore resending. With an error response, no batch was created. - Credit: only
found: trueis charged, at the same cost and from the same allowance as the CPF Simple Lookup. Invalid, duplicate, not found andskippeditems are not. - Data source: the same as the CNPJ Lookup: the monthly public database of the Receita Federal, processed by OpenCNPJ. Data may be out of date until the next monthly update.
- Sample data: the CNPJs and the data above are fictional.
Updated on October 15, 2026