View or edit on GitHub
This page is synchronized from trase/data/brazil/logistics/cnpj/receita_federal_do_brasil/cnpj_update_guide.md. Last modified on 2026-08-05 15:56 CEST by Harry Biddle.
Please view or edit the original file there; changes should be reflected here after a midnight build (CET time),
or manually triggering it with a GitHub action (link).
Updating the CNPJ dataset
End-to-end guide for adding a new version of the RFB CNPJ dataset (the dataset
that ends up registered in Athena as s3_big_data.cnpj_<YYYY>_<MM>).
What the process looks like
There are four steps; each writes its output to a place that is fixed by convention, so the next step can find it. For downloading, its much faster (30mins vs 5h) if done from Brazil, or using a VPN connected to Brazil.
| # | Step | What it does | Output |
|---|---|---|---|
| 1 | Download the raw zip files | ~36 zip files (~7.6 GB) | s3://trase-storage/brazil/logistics/cnpj/receita_federal_do_brasil/originals/<YYYY-MM>/compressed/ |
| 2 | Uncompress, fix encoding, upload | Each zip unzipped to CSV, converted to UTF-8 encoding, uploaded | …/originals/<YYYY-MM>/uncompressed/ |
| 3 | Update the dbt pipeline | Add the new version's original source files (bronze) + pre-processed (silver) + final (gold) models (copy-and-rename from the previous version) | code in this repo |
| 4 | Run the pipeline | dbt reads the bronze CSVs, builds silver tables, builds the gold table, and registers the gold table in Glue/Athena | …/gold/cnpj_<YYYY>_<MM>/cnpj_<YYYY>_<MM>_gold.parquet and an Athena table s3_big_data.cnpj_<YYYY>_<MM> |
The rest of this document walks through each step.
Step 1 — Download the raw files from the Brazilian Tax Agency (RFB)
As of 2026-06, RFB publishes the CNPJ open data on a Nextcloud public share (a data sharing system):
https://arquivos.receitafederal.gov.br/index.php/s/YggdBLfdninEJX9
If the above link doesn't work, try going through the national open data portal (https://dados.gov.br/dados/conjuntos-dados/cadastro-nacional-da-pessoa-juridica---cnpj) -> Recursos -> Inscrições no CNPJ -> Acessar o recurso.
Currently, the share root contains:
- one monthly folder per release —
2023-05/, …,2026-06/— each with ~36 zip files (Empresas{0..9}.zip,Estabelecimentos{0..9}.zip,Socios{0..9}.zip,Simples.zip, and the reference tablesCnaes / Motivos / Municipios / Naturezas / Paises / Qualificacoes), totalling ~7.6 GB per month; - a single aggregate
cnpj.tar.gzat the root (~59.5 GB, probably the full historical bundle).
Speed: download from Brazil
The data sharing service limits download speed by geography. Observed single-connection speeds:
| Source | Speed | Monthly set (7.6 GB) |
|---|---|---|
| International IP | ~0.43 MB/s | ~5 h |
| Brazil IP | ~3-6 MB/s | ~40 min |
→ Prefer a Brazilian connection. A VPN to Brazil works (for example, NordVPN). Once your laptop is on a Brazilian IP, the rest of this step runs in any terminal.
Download a monthly release (~7.6 GB, ~40 min)
The commands below run on your laptop. Run each block one at a time, check the output is what you expect, then move on to the next block.
1. Pick the month and set the paths
Replace MONTH with the year-month folder name as it appears in the share (for
example 2026-06). Everything else uses $MONTH and stays the same.
TOKEN=YggdBLfdninEJX9 # share token (between /s/ and ? in the URL)
MONTH=2026-06 # <<-- adjust to the month you are downloading
BASE=https://arquivos.receitafederal.gov.br/public.php/webdav/$MONTH
DEST=s3://trase-storage/brazil/logistics/cnpj/receita_federal_do_brasil/originals/$MONTH/compressed
LOCAL=~/cnpj-download/$MONTH # local folder the zips will land in
mkdir -p "$LOCAL" && cd "$LOCAL"
2. Ask the share for the list of zip files
This writes the file names to files.txt. Then cat it to confirm: you should see
~36 lines, all *.zip (Empresas0.zip, Estabelecimentos0.zip, etc.).
curl -s -u "$TOKEN:" -X PROPFIND -H 'Depth: 1' "$BASE/" \
| grep -oE "/public.php/webdav/$MONTH/[^<]+\.zip" | sed 's#.*/##' | sort -u > files.txt
cat files.txt
wc -l files.txt
3. Download the zips, one at a time
If your connection drops, just re-run this block — already-finished files are
skipped, half-finished ones resume from where they left off (that is what -C -
and --retry do).
while read -r f; do
echo ">>> $f"
curl -C - --retry 10 -u "$TOKEN:" -O "$BASE/$f"
done < files.txt
When it finishes, check you really have all the zips:
ls -1 *.zip | wc -l # should match the line count from step 2
du -sh . # should be ~7-8 GB
4. Upload to S3
aws s3 cp "$LOCAL" "$DEST/" --recursive --exclude '*' --include '*.zip'
# verify
aws s3 ls "$DEST/"
When all the .zip objects show up in S3, Step 1 is done. You can delete the
local copies (rm -rf "$LOCAL").
Background — why the download isn't just a button click
Nextcloud renders the file list with JavaScript over WebDAV — there are no
<a href> download links in the HTML to copy. The commands above talk to the
WebDAV API directly, using the share token from the URL (YggdBLfdninEJX9)
as the HTTP username with an empty password.
⚠️ Do not use the
…/index.php/s/<token>/download?path=…URL — it routes through Nextcloud's on-the-fly zip packer and frequently hangs at 0 bytes.
Step 2 — Uncompress, normalise to UTF-8, upload
The zips on S3 are not directly readable by the pipeline: each one must be unzipped into a CSV, and the CSVs come in a mix of UTF-8 and latin-1 encodings, which has to be normalised to UTF-8 before they can be joined together.
Both are handled by the script
rfb_uncompress_and_upload.sh in this folder. It works one
file at a time, abort-on-failure, and deletes each local file as soon as it has been
uploaded — so it can run on a small disk and you don't end up with half-finished uploads.
Recommended: run it on a SageMaker terminal. SageMaker is on the same AWS network as S3, so the upload is fast and free, and it doesn't tie up your laptop for the duration.
# inside a SageMaker terminal (or anywhere with the trase repo + aws cli):
cd trase/data/brazil/logistics/cnpj/receita_federal_do_brasil/
./rfb_uncompress_and_upload.sh 2026-02 # use the date you downloaded
The script writes the cleaned CSVs to:
s3://trase-storage/brazil/logistics/cnpj/receita_federal_do_brasil/originals/<YYYY-MM>/uncompressed/
That folder is what the dbt pipeline reads from in the next step.
Step 3 — Update the dbt pipeline for the new version
The pipeline already contains entries for past versions (2025_02_04, 2023_08_12,
2022_11_24, 2019_02). You just copy the most recent year's block and rename the
date. There are three places to edit, all using the same <YYYY>_<MM> (e.g. 2026_02).
Tip: do a copy-and-rename on the previous version's block. The schemas are identical across years, so no logic needs to be changed.
3a. Bronze — raw CSV sources
Add one source entry per CSV (one each for cnaes, empresas, estabelecimentos,
motivos, municipios, naturezas, paises) under sources: - name: source_brazil,
following the existing pattern. Every path contains <YYYY-MM> — that's the date you used
in Step 2.
3b. Silver — one cleaned table per CSV
Folder:
trase/data_pipeline/models/brazil/logistics/cnpj/silver/
Copy the previous year's subfolder (e.g. cnpj_2025_02_04/) to cnpj_<YYYY>_<MM>/ (e.g.
cnpj_2026_02/). Inside, rename each file and change the date in the dbt.source(...)
call — everything else (the cleaning logic) is shared via
trase/data/brazil/logistics/cnpj/cnpj_preprocessing.py and needs no editing.
3c. Gold — the final joined table
File:
trase/data_pipeline/models/brazil/logistics/cnpj/gold/cnpj_<YYYY>_<MM>_gold.py
Copy cnpj_2025_02_04_gold.py to cnpj_<YYYY>_<MM>_gold.py and update the date in every
dbt.ref(...) call. Then in the schema YAML
_schema_cnpj_receita_federal_do_brasil.yml
add the matching cnpj_<YYYY>_<MM>_gold model entry — again, copy from the previous year
and update the date.
Make sure the new gold model entry carries the post-hook line:
post_hook: "{{ register_glue_table() }}"
This is what triggers automatic registration in Athena at the end of Step 4.
Sanity check
Before running anything, ask dbt to confirm the new YAML/Python parses cleanly:
cd trase/data_pipeline
./dbt parse --target production
No error means the new version is wired in.
Step 4 — Run the pipeline
Recommended: from a SageMaker terminal. Building the gold table involves several joins over the full CNPJ data and uses a lot of memory and temporary disk — SageMaker instances are sized for this, and the dbt wrapper auto-configures DuckDB's memory limit to 70% of the instance RAM.
Suggested instance sizing:
- RAM: 128 GB
- Storage: 100 GB or more (temporary files spill to disk during the joins)
Expected runtime: between 30 minutes and 1 hour on an instance of that size.
cd trase/data_pipeline
# build the new bronze + silver + gold for this version only
./dbt run --target production --select +cnpj_<YYYY>_<MM>_gold
# example for 2026_02:
./dbt run --target production --select +cnpj_2026_02_gold
What happens, in order:
- The "bronze" sources are loaded directly from the CSVs on S3 (no parquet is written — sources are read on the fly).
- Each "silver" model writes a cleaned parquet to
s3://trase-storage/brazil/logistics/cnpj/silver/cnpj_<YYYY>_<MM>/. - The gold model joins them and writes the final parquet to
s3://trase-storage/brazil/logistics/cnpj/gold/cnpj_<YYYY>_<MM>/cnpj_<YYYY>_<MM>_gold.parquet. - The post-hook on the gold model calls
register_glue_table(), which inspects the new parquet and registers (or refreshes) the Athena tables3_big_data.cnpj_<YYYY>_<MM>. You'll see a line in the dbt output:
**** Registered Glue/Athena table 's3_big_data.cnpj_<YYYY>_<MM>' (N columns) -> s3://… ****
That's it — the new version is queryable from Metabase / Athena straight away (see How to explore the data in the dataset doc), and the new version's S3 path should be added to the Version history list in that same doc.