# Brazil CNPJ Sanctions — CEIS, CNEP, CEPIM and Leniency (`kadev-code/br-cnpj-sanctions`) Actor

Screen Brazilian legal entities against four official CGU public downloads, with checksum validation and daily caching.

- **URL**: https://apify.com/kadev-code/br-cnpj-sanctions.md
- **Developed by:** [Kadev Code](https://apify.com/kadev-code) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 cnpj checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Brazil CNPJ Sanctions Screening

Screen Brazilian companies against the official **CEIS**, **CNEP**, **CEPIM** and
**Acordos de Leniência** public downloads from the CGU Portal da Transparência.
This Actor accepts a batch of CNPJs and produces one structured dataset item for
each distinct normalized identifier. It requires no Portal API key and runs with
Python 3.12 and the Apify SDK, without a browser.

This is an **informational lookup and does not replace an official certificate**
(consulta informativa; não substitui certidão oficial). Review source records and
obtain the appropriate official certificate before making a procurement or legal
decision. A list occurrence is not a risk score or an automatic rejection rule.

### Input

```json
{"cnpjs": ["00.000.000/0001-91", "11.222.333/0001-81"]}
```

Provide between 1 and 1,000 strings. Standard or partial masks (for example,
`00.000.000/000191`), unformatted numeric CNPJs,
and alphanumeric CNPJs are supported. The two check digits are validated using
the [Receita Federal algorithm](https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/documentos-tecnicos/cnpj/manual-dv-cnpj.pdf).
Validation checks the identifier's syntax and checksum, not its registration or
operating status. Duplicate normalized identifiers are delivered and billed once.
Missing local input uses the schema's default example, a public company CNPJ.
An empty list or malformed top-level input produces a free input-error item.

### Output and interpretation

The following is an illustrative shape, not a live statement about this company:

```json
{
  "cnpj": "00000000000191",
  "companyName": null,
  "validCnpj": true,
  "sanctioned": false,
  "activeSanction": false,
  "lists": [],
  "records": [],
  "stale": false,
  "staleSources": [],
  "partialData": false,
  "warnings": [],
  "sourceQuality": {},
  "datasetDate": {
    "CEIS": "2026-10-21",
    "CNEP": "2026-10-21",
    "CEPIM": "2026-10-21",
    "Acordos de Leniência": "2026-10-22"
  },
  "error": null
}
```

Each matching record contains `list`, `cnpj` (the source identifier), `matchType`,
`sanctionType`, `sanctioningBody`, `sanctionScope` (ABRANGÊNCIA DA SANÇÃO when
published), `startDate`, `endDate`, `dateUnknown`, `active` and `legalBasis`.
Matching includes every establishment sharing the first eight CNPJ characters:
`matchType: "exact"` matches the requested identifier; `"sameRoot"` identifies
another branch or headquarters in that company root. Review the source's scope
before deciding whether a sanction legally applies to a specific establishment.
Dates use ISO format; absent or invalid dates and unavailable fields are `null`.
CEPIM does not publish start/end dates in this download, so
both remain null. For leniency, `legalBasis` contains the published agreement
terms; for CEPIM, it contains the reason for the impediment.

Every record has the same 36 keys. The table below includes all fields. Text is
trimmed and CPF patterns are redacted, including in company names and remarks.
Empty or unavailable fields are `null`; dates are ISO `YYYY-MM-DD`. Invalid
publication/information/transit dates are also `null` and do not change the
activity calculation, which uses only the original start/end bounds.

| Field | Type (nullable unless stated) | Source / meaning |
| --- | --- | --- |
| `list` | string, required | Official list name |
| `cnpj` | string, required | Validated source CNPJ |
| `matchType` | string, required | `exact` or `sameRoot` |
| `sanctionType` | string | CEIS/CNEP CATEGORIA DA SANÇÃO; source category for CEPIM/leniency |
| `sanctioningBody` | string | ÓRGÃO SANCIONADOR; CEPIM ÓRGÃO CONCEDENTE |
| `sanctionScope` | string | CEIS/CNEP ABRAGÊNCIA DA SANÇÃO (published spelling); ABRANGÊNCIA is also supported |
| `startDate`, `endDate` | date string | Sanction dates or leniency agreement start/end |
| `dateUnknown` | boolean, required | A nonempty start/end bound could not be parsed |
| `active` | boolean, required | Activity on the screening date, using start/end bounds |
| `legalBasis` | string | FUNDAMENTAÇÃO LEGAL, CEPIM reason, or leniency terms |
| `registry` | string | CEIS/CNEP CADASTRO |
| `sanctionId` | string | CEIS/CNEP CÓDIGO DA SANÇÃO |
| `personType` | string | CEIS/CNEP TIPO DE PESSOA (company rows only) |
| `sanctionedName` | string | CEIS/CNEP NOME DO SANCIONADO |
| `reportedName` | string | CEIS/CNEP NOME INFORMADO PELO ÓRGÃO SANCIONADOR |
| `companyName` | string | RAZÃO SOCIAL - CADASTRO RECEITA; CEPIM NOME ENTIDADE |
| `tradeName` | string | NOME FANTASIA - CADASTRO RECEITA, including the leniency en-dash spelling |
| `sanctionedUf` | string | Always `null` in the verified downloads: no company-state column is published |
| `sanctioningBodyUf` | string | CEIS/CNEP UF ÓRGÃO SANCIONADOR; this is the authority's state |
| `sanctioningBodySphere` | string | CEIS/CNEP ESFERA ÓRGÃO SANCIONADOR |
| `processNumber` | string | CEIS/CNEP/leniency NÚMERO DO PROCESSO |
| `publicationDate` | date string | CEIS/CNEP DATA PUBLICAÇÃO |
| `publicationSource` | string | CEIS/CNEP PUBLICAÇÃO |
| `publicationDetails` | string | CEIS/CNEP DETALHAMENTO DO MEIO DE PUBLICAÇÃO |
| `transitDate` | date string | CEIS/CNEP DATA DO TRÂNSITO EM JULGADO |
| `fineValue` | number | CNEP VALOR DA MULTA in BRL; decimal comma/grouping dots become a JSON number; invalid text becomes `null` |
| `informationDate` | date string | CEIS/CNEP DATA ORIGEM INFORMAÇÃO; leniency DATA DA INFORMAÇÃO |
| `informationSource` | string | CEIS/CNEP ORIGEM INFORMAÇÕES |
| `remarks` | string | CEIS/CNEP OBSERVAÇÕES |
| `transferAgreementNumber` | string | CEPIM NÚMERO CONVÊNIO |
| `impedimentReason` | string | CEPIM MOTIVO DO IMPEDIMENTO; also in `legalBasis` |
| `agreementId` | string | Leniency ID DO ACORDO |
| `agreementStatus` | string | Leniency SITUAÇÃO DO ACORDO DE LENIÊNICA (published spelling) |
| `agreementTerms` | string | Leniency TERMOS DO ACORDO; also in `legalBasis` |
| `agreementEffects` | array | Leniency Efeitos.csv rows joined by agreement ID; each has nullable `effect` (EFEITO DO ACORDO DE LENIENCIA) and `details` (COMPLEMENTO) |

Identifiers and process/agreement numbers remain strings to preserve leading
zeros. There is no separate decision-date column in the checked files;
`transitDate` is the published final-decision date. Leniency's responsible body
is in `sanctioningBody`; its dates are `startDate` and `endDate`. An absent effect
file or an agreement without matching effects produces `agreementEffects:null`.
Other lists have `agreementEffects:null` as well.

At item level, `companyName` comes from the **first exact record**, or the first
record if there is no exact match. Within that selected record, the Actor uses
`companyName`, then `sanctionedName`, then `reportedName`. It returns `null` if
that record has no name or there are no records. Billing-status items retain
the delivered item's name. The Actor uses only the names already present in
CGU downloads; it makes no Receita or Portal API lookup.

For example, a synthetic CNEP match can contain this record (all values below
are invented test data, not a real sanctions finding):

```json
{
  "list": "CNEP",
  "cnpj": "00000000000191",
  "matchType": "exact",
  "sanctionType": "Multa",
  "sanctioningBody": "Example authority",
  "sanctionScope": "Nacional",
  "startDate": "2026-01-01",
  "endDate": "2026-12-31",
  "dateUnknown": false,
  "active": true,
  "legalBasis": "Synthetic legal basis",
  "registry": "CNEP",
  "sanctionId": "00012",
  "personType": "J",
  "sanctionedName": "Example company",
  "reportedName": "Example company as reported",
  "companyName": "Example company Ltda",
  "tradeName": "Example trade name",
  "sanctionedUf": null,
  "sanctioningBodyUf": "DF",
  "sanctioningBodySphere": "Federal",
  "processNumber": "001/2026",
  "publicationDate": "2026-01-02",
  "publicationSource": "DOU",
  "publicationDetails": "Section 1",
  "transitDate": "2026-01-03",
  "fineValue": 1234567.89,
  "informationDate": "2026-10-22",
  "informationSource": "CGU",
  "remarks": "Synthetic observation",
  "transferAgreementNumber": null,
  "impedimentReason": null,
  "agreementId": null,
  "agreementStatus": null,
  "agreementTerms": null,
  "agreementEffects": null
}
```

The containing item's `companyName` would be `"Example company Ltda"` if this
were its first exact record. A leniency record can instead have
`"agreementEffects":[{"effect":"Synthetic effect","details":null}]`.

`sanctioned` and `activeSanction` mean **at least one active matching record**.
Historical records remain visible with `active:false`. Activity is evaluated on
the run's UTC date, including the start/end dates themselves, even for cached
records. Missing bounds are open-ended; an invalid date sets `dateUnknown:true`
and conservatively keeps the record active. This date-based indicator includes
leniency and CEPIM occurrences and is not a legal enforceability decision.
A successful no-match result has all four dataset dates and `error:null`.
On source/cache failure the indicators are `null` (unknown), with an explicit
error and no screening fee. Never interpret an error as clearance.

Every output item includes `partialData` and `warnings`. If any used base has
discarded malformed rows without a recoverable CNPJ within the 1% tolerance,
`partialData:true` applies to every screening using that base, including matches
and no matches. `warnings` contains one readable notice per affected base:

```json
{
  "code": "SOURCE_PARTIAL",
  "source": "CEPIM",
  "rejectedRows": 1,
  "message": "CEPIM: 1 malformed row(s) discarded; at least one has no recoverable CNPJ, so occurrences may be missing."
}
```

`rejectedRows` counts **all malformed rows discarded in that base**, including
those with known CNPJs; it is not a count of unidentified rows or missing sanctions.
Clean bases produce `partialData:false` and `warnings:[]`. The flag uses the
snapshot actually screened, including daily cache and stale fallback; it does
not describe a failed refresh that was replaced by another snapshot. Status
items of `type:"billingStatus"` inherit these fields from the delivered screening.
Input/source errors and unprocessed items that use no bases have `partialData:false`
and `warnings:[]`; that flag alone does not establish a successful screening.
Always check `error`, `partialData` and `stale` alongside the sanction indicators.

### Official sources and freshness

The following official pages and their dated download routes were checked on
2026-10-22. The Actor reads the page's embedded `arquivos.push` date metadata and
uses the latest published date, rather than guessing today's archive name.

| List | Official discovery page | Verified download route |
| --- | --- | --- |
| CEIS | [CEIS downloads](https://portaldatransparencia.gov.br/download-de-dados/ceis) | [20261022](https://portaldatransparencia.gov.br/download-de-dados/ceis/20261022) |
| CNEP | [CNEP downloads](https://portaldatransparencia.gov.br/download-de-dados/cnep) | [20261022](https://portaldatransparencia.gov.br/download-de-dados/cnep/20261022) |
| CEPIM | [CEPIM downloads](https://portaldatransparencia.gov.br/download-de-dados/cepim) | [20261021](https://portaldatransparencia.gov.br/download-de-dados/cepim/20261021) |
| Leniency | [Agreement downloads](https://portaldatransparencia.gov.br/download-de-dados/acordos-leniencia) | [20261022](https://portaldatransparencia.gov.br/download-de-dados/acordos-leniencia/20261022) |

These routes redirected to
`https://dadosabertos-download.cgu.gov.br/PortalDaTransparencia/saida/<slug>/<YYYYMMDD>_<name>.zip`.
Names were `CEIS`, `CNEP`, `CEPIM` and `AcordosLeniencia`, respectively. Agreements
contain both an `Acordos.csv` and an auxiliary `Efeitos.csv`; effects are joined
to validated company agreements by ID. Windows-1252 and UTF-8 CSV files are supported.

Each base is normalized once when its published date changes and retained as
compressed JSON in the named key-value store `br-cnpj-sanctions-cache-v3`.
This store belongs to the **user's Apify account**: it is reused across that
account's sequential runs, not shared globally between customers. Version 3
uses new cache keys for enriched records, with a root index and row-quality
metadata; v1/v2 data is not reused because it lacks the complete fields.
The first run after this upgrade downloads all four bases again.
Subsequent sequential runs reuse snapshots during the UTC day.
The next day checks publication metadata and downloads a ZIP only if the source
date changed. There is no download per CNPJ. Dates describe the publisher's
snapshot, not the time of this run; weekends can legitimately retain older dates.
If refreshing a base fails, a validated prior snapshot can be used for at most
seven days after its `datasetDate` (and last successful check). The item exposes
`stale:true`, `staleSources` and the unchanged `datasetDate` for each list. Failed
refreshes never advance that date or the successful-check timestamp. An accepted
fallback screening uses the normal event price. Expired, corrupt,
future-dated or incompatible cache cannot authorize a result: return an error
without a fee if any source lacks an acceptable snapshot. Concurrent cold runs
may duplicate a refresh because the key-value store does not provide a distributed
lock; serialize scheduled runs when avoiding that duplication is essential.

### Privacy, limits and errors

Only validated company identifiers enter the normalized cache. Individual rows
are excluded; company names and the documented fields are retained through an
explicit column allowlist. Raw rows and unrelated columns are not retained.
CPF patterns in all text fields are redacted. An invalid CPF-shaped input is returned as
`cnpj: null`, without echoing the supplied personal identifier. Raw downloads
remain in memory only. The dataset contains the requested company screening
results and follows your Apify storage retention settings.

Requests have a 15-second connection timeout, a 40-second idle-operation timeout,
and a 60-second global deadline covering redirects, streaming, all retries and
backoff. Loading all four bases has an additional 240-second global deadline.
If that deadline cancels refresh, a cache-only recovery validates all four
snapshots and returns them with stale metadata; no further HTTP is attempted.
Recovery reads the account's KVS after the refresh deadline. If any snapshot is
unusable, the result remains `SOURCE_TIMEOUT` and is not charged.
Up to three attempts use exponential backoff on transient failures. ZIPs are
limited to 32 MiB compressed and 160 MiB expanded; normalized cache is capped at
64 MiB per base. The Actor checks headers and rejects empty or changed schemas.
Malformed rows are discarded and counted; more than 1% rejects the entire base
with `SOURCE_TOO_MANY_BAD_ROWS`. `sourceQuality` reports total, rejected and
invalid-date row counts, `unknownIdentityRows` (whether any discarded row lacks
a recoverable CNPJ), and `uncertainRootCount`. A discarded row with a known
company root prevents screening that root (`SOURCE_INCOMPLETE_ROWS`). An
unidentified discarded row consumes the same 1% allowance and does not block
every company; it sets `partialData:true` and a `SOURCE_PARTIAL` warning for every
screening using the affected base. Tolerated rejects mean occurrences may be
missing, so `sanctioned:false` with `partialData:true` is a partial lookup and
does not establish absence of sanctions. These warnings do not change `error`
or the normal screening fee. If all rejected rows have recoverable CNPJs,
`partialData` remains false; known affected roots still return
`SOURCE_INCOMPLETE_ROWS` and are not charged. Above 1%, refresh fails and uses a
validated snapshot up to seven days old before returning an error. Cache encoding
above 64 MiB follows that same
fallback rule. Error items are not charged.
The internal `uncertainRoots` array remains in the account cache to identify
affected queries, but is omitted from dataset items: aggregate counts explain
source quality without repeating unrelated company identifiers in every result.
Invalid dates become null, are counted separately and preserve the record.
Common errors include
`INVALID_CNPJ`, `SOURCE_UNAVAILABLE`, `SOURCE_SCHEMA_CHANGED`, `SOURCE_EMPTY` and
`SOURCE_TIMEOUT` and `CACHE_OR_SOURCE_UNAVAILABLE`.
Permission to read/write the account's named cache is needed. The platform's
**limited permissions** mode was not tested by this enrichment change; see [verification.md](verification.md) for that
launch prerequisite. A permissions failure returns an error without a fee.
Foreign entities without a valid Brazilian CNPJ cannot be matched.
The Actor definition defaults to 256 MiB, with a 256–512 MiB allocation range.
Configure a 300-second run timeout in Console when preparing the unpublished Actor.

### Pricing and SDK integration

Proposed pricing is **$0.002 per `cnpj-checked` event** ($2 per 1,000 complete,
valid screenings). A completed no-match lookup is a delivered screening result.
Tolerated partial-data screenings use the same fee, including no-match results;
`SOURCE_PARTIAL` is a warning, not a billing error.
Invalid inputs, unavailable/empty sources and failed deliveries are not billed;
there is no startup event. The Actor checks the SDK's remaining event allowance
before loading data and before each valid screening, pushes the result, then
calls `Actor.charge(event_name="cnpj-checked", count=1, idempotency_key=cnpj)`.
The normalized CNPJ prevents duplicate billing inside a run. On a charge failure,
the delivered result remains, followed by a `type:"billingStatus"` item with
the normalized `cnpj`, `validCnpj:true`,
`CHARGE_FAILED_AFTER_DELIVERY` and `chargeConfirmed:false` (confirmation was
unavailable; this does not assert a refund). Further valid CNPJs receive
`NOT_PROCESSED_CHARGING_FAILURE` errors. Spending limits similarly produce
`NOT_PROCESSED_SPENDING_LIMIT` items. Status/error items generate no events.
Consumers should branch on `type:"billingStatus"`: it reports billing state
for the preceding screening and does not contain screening `records`.
Configure the event in Apify Console before enabling monetization;
the repository's pricing document is a proposal, not a publication action.

The current API was verified against the official
[Actor.charge reference](https://docs.apify.com/sdk/python/reference/class/Actor#charge),
[ChargingManager reference](https://docs.apify.com/sdk/python/reference/class/ChargingManager)
and [ChargeResult reference](https://docs.apify.com/sdk/python/reference/class/ChargeResult),
and exercised with pinned SDK 4.1.0. The code uses the result's
`event_charge_limit_reached` attribute and
`calculate_max_event_charge_count_within_limit`, which returns `None` when
unlimited. See [.actor/pricing.md](.actor/pricing.md) for current market comparisons
and cost assumptions.

### Local development and verification

Create a Python 3.12 virtual environment and install `requirements-dev.txt`.
From the repository root, with that environment activated:

```sh
python -m pytest actors/br-cnpj-sanctions/tests -q
python actors/br-cnpj-sanctions/tests/smoke.py
```

Tests use local synthetic CSV fixtures and mocked HTTP; external socket connections
are blocked (Windows asyncio's internal loopback wake-up sockets are allowed).
The smoke script runs the real `main()` and SDK with default input and local
dataset/KVS storage, replacing only official HTTP responses with synthetic
fixtures. It prints a clearly labelled synthetic output item and incurs no charge.
Its ignored `.local-smoke` directory can be inspected after execution.
For a live local run, change into this Actor directory and run `python -m src`;
that command accesses official public downloads. No cloud deployment, publication,
real paid event, or Docker build is implied by local verification.

# Actor input Schema

## `cnpjs` (type: `array`):

Up to 1,000 company identifiers, with or without the standard mask. Numeric and alphanumeric CNPJs are accepted. Duplicate normalized CNPJs are checked once; CPF input is never echoed.

## Actor input object example

```json
{
  "cnpjs": [
    "00.000.000/0001-91"
  ]
}
```

# Actor output Schema

## `results` (type: `string`):

Screenings and supplemental billing status rows in the default dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "cnpjs": [
        "00.000.000/0001-91"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kadev-code/br-cnpj-sanctions").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "cnpjs": ["00.000.000/0001-91"] }

# Run the Actor and wait for it to finish
run = client.actor("kadev-code/br-cnpj-sanctions").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "cnpjs": [
    "00.000.000/0001-91"
  ]
}' |
apify call kadev-code/br-cnpj-sanctions --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kadev-code/br-cnpj-sanctions"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/BkTbz8DVKnuBa8SH7/builds/z7oL5r2R1sq4CWqQF/openapi.json
