The DOF note archive

What ships in the notas-archivo release, and how it grows

Published

July 18, 2026

Abstract

The explorations and tools on this site are not built from a static snapshot: they read from a GitHub Release, tagged notas-archivo, that packages the daily notes index of the Diario Oficial de la Federación as downloaded by dofjson’s –archivo mode. This page documents what that release contains, the monthly workflow that keeps it growing, and shows its current contents fetched live from GitHub at render time. It is the same archive that DOF Titles is built from — every figure on that page, from the 1,221,451 notes to the vocabulary by decade, is computed from these files.

Open in Colab

1 One release, one file per publication day

The repository keeps its collected Diario Oficial data out of git and in a single GitHub Release instead, notas-archivo: the raw archive is large, grows every month, and would not sit comfortably as committed files. Each asset in that release is a .tgz of one JSON file per publication day, produced by dofjson’s incremental --archivo mode against the DOF’s open-data service — the day’s index of what was published, never a note’s full content or its scanned images.

One asset, one span of time

A closed year packs into a single notas-YYYY.tgz; the year under way ships instead as one notas-YYYY-MM.tgz per finished month, until it closes.

One file, one day

Inside each archive, one JSON file per publication day — DDMMYYYY-notas.json — holding that day’s morning, evening and extraordinary editions.

One record, one note

Each note carries its codNota identifier, titulo, the section and page it was printed on, its issuing branch, and whether HTML, PDF or scanned images exist for it.

A single note record looks like this (trimmed to the fields the site’s pages actually use):

{
  "codNota": 5793639,
  "titulo": "Acuerdo por el que se establecen acciones para la primera etapa...",
  "fecha": "15-07-2026",
  "codOrgaUno": "PE",
  "nombreCodOrgaUno": "PODER EJECUTIVO",
  "codSeccion": "UNICA",
  "pagina": 5,
  "existeHtml": "S",
  "existeImagen": "S",
  "existePdf": "S"
}

Stub entries without a title — an internal bookkeeping artifact of the service — are filtered out before the file is written, using the same quita_notas_sin_titulo helper described in From the gazette to Markdown.

2 How the archive grows

The release is kept current by a scheduled workflow, notas-archivo.yml, that runs once a month, at 09:00 UTC on the 1st. In the ordinary case it downloads the month that just ended with dofjson --archivo --desde ... --hasta ... and uploads it as notas-YYYY-MM.tgz, overwriting any asset of the same name (--clobber), so a re-run of the same month is harmless. January 1st is a special case: the month that just ended is December, so instead of one more monthly asset the workflow downloads the entire year that just closed, publishes it as a single notas-YYYY.tgz, and deletes the twelve monthly assets that covered it — folding a year’s worth of monthly archives into the one closed-year asset the pattern above expects. The workflow can also be run by hand, for a specific month, through its workflow_dispatch input — useful for backfilling or forcing a re-run. None of this depends on the workflow always succeeding: dofjson --archivo resumes from where a previous attempt left off, so a failed or partial run is picked up cleanly the next time it fires.

3 The release today

Rather than describe the current contents in prose — which would be stale the day after this page is rendered — the cell below asks GitHub directly:

Code
import re

import pandas as pd
import requests

resp = requests.get(
    "https://api.github.com/repos/INGEOTEC/LegalIA/releases/tags/notas-archivo",
    timeout=30,
)
release = resp.json()
assets = sorted(release["assets"], key=lambda a: a["name"])

rows = []
for a in assets:
    m = re.match(r"notas-(\d{4})(?:-(\d{2}))?\.tgz", a["name"])
    year, month = m.group(1), m.group(2)
    rows.append(
        {
            "asset": a["name"],
            "span": year if month is None else f"{year}-{month}",
            "kind": "year" if month is None else "month",
            "size_MB": round(a["size"] / 1_000_000, 2),
        }
    )
table = pd.DataFrame(rows)
print(
    f"{len(table)} assets, spanning {table.span.min()}\u2013{table.span.max()}: "
    f"{(table.kind == 'year').sum()} closed years, "
    f"{(table.kind == 'month').sum()} months of the year under way "
    f"({table.size_MB.sum():.0f} MB total)."
)
table.tail(8)
115 assets, spanning 1917–2026-06: 109 closed years, 6 months of the year under way (59 MB total).
asset span kind size_MB
107 notas-2024.tgz 2024 year 1.09
108 notas-2025.tgz 2025 year 1.08
109 notas-2026-01.tgz 2026-01 month 0.05
110 notas-2026-02.tgz 2026-02 month 0.07
111 notas-2026-03.tgz 2026-03 month 0.09
112 notas-2026-04.tgz 2026-04 month 0.09
113 notas-2026-05.tgz 2026-05 month 0.09
114 notas-2026-06.tgz 2026-06 month 0.11

The tail of the table is the interesting part: it is exactly the boundary described above, the last couple of closed years followed by the monthly assets accumulated so far for the year in progress. Run this page again next month and one more row will have appeared there.

4 From release to the DOF Titles page

Nobody points a plotting library at the release directly: the .tgz files hold one JSON per day, which is the right shape for incremental downloading and resuming but the wrong shape for analysis. A local, gitignored notas-archivo/ extracts the release’s assets into that per-day layout, and website/scripts/aggregate_titles.py walks it once, tokenizing and counting titles, to write six small CSVs — notes_per_year.csv, days_with_notes.csv, title_length.csv, first_words.csv, headings.csv and terms_by_decade.csv — into website/data/. Unlike the raw archive, those CSVs are committed, which is what lets DOF Titles rebuild without re-downloading anything.

That page’s every figure traces back to this same release: its 1,221,451 notes over 1917–2025 are exactly what the closed-year assets above account for, and the months of 2026 already sitting in the release — visible in the table’s last rows — are the raw material for the fuller update that page still owes once 2026 itself closes.

5 Getting the raw archive yourself

Any asset can be pulled without cloning the repository:

gh release download notas-archivo -p notas-2025.tgz
tar xzf notas-2025.tgz

Without gh, every entry of the assets list in the API response used above carries a browser_download_url — a direct link to that .tgz. Pick the one you want out of that same response:

url = next(
    a["browser_download_url"] for a in assets if a["name"] == "notas-2025.tgz"
)
# https://github.com/INGEOTEC/LegalIA/releases/download/notas-archivo/notas-2025.tgz

and follow it with any HTTP client:

curl -LO https://github.com/INGEOTEC/LegalIA/releases/download/notas-archivo/notas-2025.tgz
tar xzf notas-2025.tgz

To extend a local copy going forward instead of downloading a fixed snapshot, dofjson --archivo is the same tool the monthly workflow runs, and it is resumable in the same way: interrupt it and rerun it, and it picks up only the days it is still missing.

6 Downloading every title in the archive

Beyond browsing one release asset at a time, dofjson ships a --titulos mode that builds a compact codNota + titulo + fecha dataset out of every note ever published: it downloads each notas-archivo asset straight into memory and reads its daily JSON indexes without ever writing them to disk, keeping only those three fields from every note (titulo is Spanish for “title”, fecha for “date”) — codNota to fetch the note’s full content later (dofjson.client.get_nota), titulo for exploratory analysis of the titles themselves with other techniques (in the spirit of DOF Titles), and fecha to place each title in time. The result is a single gzip-compressed JSONL file — the 1.2 million-plus notes in the table above fit in a few tens of MB, small enough to move to a Colab GPU runtime for experiments.

dofjson --titulos                    # -> titulos/titulos.jsonl.gz

or directly from Python, with dofjson.titulos.download_titulos:

from pathlib import Path
from dofjson.titulos import download_titulos

download_titulos(Path("titulos.jsonl.gz"))
import gzip, json

with gzip.open("titulos.jsonl.gz", "rt", encoding="utf-8") as f:
    notas = [json.loads(line) for line in f]
notas[0]
# {"codNota": 4434476, "titulo": "CIRCULAR nº. 164, relativa a los asuntos
#  que se traten por telégrafo con esta Secretaría", "fecha": "23-03-1917"}

Methodological note

This page is a Jupyter notebook that queries the GitHub REST API for the notas-archivo release at render time; the table above reflects the release’s contents at the moment this page was last rendered, not necessarily today. The archive, the workflow that maintains it and the aggregation script that feeds the DOF Titles page are all part of the repository.

The archive is itself a product of the collaboration that runs through the whole project: the incremental --archivo downloader and the --titulos reader in dofjson, the monthly workflow that packages and publishes each release, and this page were written by the LegalIA team together with Claude, Anthropic’s coding assistant, through Claude Code, with the authors reviewing and validating each contribution before it landed.