BORME API

Engineering notes

You don't need to scrape BORME: Spain's company registry has an open-data API

2026-07-08 · 8 min read · bormeapi.com

Spain publishes every commercial-registry act — incorporations, director changes, insolvencies — in an official gazette called BORME (Boletín Oficial del Registro Mercantil). Most tools we found scrape its PDFs. It turns out you don't need to: since 2009 there is an official open-data API on boe.es that almost nobody seems to use. We just finished backfilling all of it — 9.5 million company events, 2009 → today — and these are the notes we wish we'd had at the start.

The API nobody talks about

Six traps that cost us real time

  1. The 404 lies about its content type. Non-publication days (weekends, holidays) return 404 — with an XML body, even when you asked for application/json. Check the status code before parsing.
  2. Missing documents return HTTP 200. A province that didn't publish gives you 200 OK with <error><descripcion>No se encontró el documento original</descripcion></error> inside. If you only check status codes, you will cache garbage.
  3. The trailing dot is a minefield. 312077 - DAMI DELUSION S.L. — is the final dot a sentence terminator or part of “S.L.”? A naive rstrip('.') corrupts thousands of company names.
  4. One paragraph = a chain of acts. A single entry routinely packs 3–6 acts: Ceses/Dimisiones. Adm. Unico: X. Nombramientos. Liquidador: X. Disolución. Extinción. Datos registrales… You need an ordered-vocabulary scanner, not a per-line regex.
  5. Provincial dialects are real. Barcelona SHOUTS IN UPPERCASE, cooperatives have a Consejo Rector instead of directors, associations a Junta Directiva, and province names carry accents that never match what users type (ARABA/ÁLAVA).
  6. Dates arrive in at least five formats (26.06.26, 4.05.09, 15/11/16, 2-10-2009, 21 DE FEBRERO DE 2006) — and pre-euro records still quote Ptas.

What worked

A deterministic parser over a closed vocabulary — no LLM in the hot path. The gazette's shorthand is a regular language over ~50 act-type labels and ~80 role labels. A scanner plus per-act regexes parses the entire 17-year corpus in minutes and is reproducible byte-for-byte. (LLMs were still useful — for drafting test fixtures.)

Measure what you didn't parse. The single best QA metric we found: consumed characters ÷ total characters, per file. Anything unconsumed ships in the output verbatim as unparsed_spans, so format drift shows up as a number going down — not as silent data loss. Long-run average: 99.9%.

Politeness scales fine. ≤1 request/second with an identifying User-Agent and retries with backoff. The full 2009→today backfill is ~160k requests — about two days of wall-clock, fully resumable (idempotent by file, month checkpoints).

Capacity-planning numbers: ~2,000–2,500 events per business day currently; ~6 GB of raw XML for the full history; 9.5M parsed events across 3.2M companies in Postgres.

The subtle part: three format epochs

The XML layout differs subtly between 2009, 2015 and 2026. Two examples we hit in production: a handful of files carry a number-less articulo holding an erratum note ( -  or - COMPANY NAME before a “Fe de erratas” / “Corrección de errores” paragraph) — reject the pair, not the file; and one 2024 day shipped registral coordinates with the T / F / S markers missing entirely. A strict parser that refuses to guess, plus a metric for what it skipped, catches every one of these as a number — we reviewed each by hand, and 17 years of gazette yielded only a handful of such cases. The format is remarkably stable; BOE deserves more credit for it.

If you'd rather not build all this yourself: we packaged the parsed feed as a typed English JSON dataset — 52 act-type enums, officer roles with names, registry coordinates — with a REST API, webhooks and an MCP server for AI agents.

Run it on Apify — $1 per 1,000 events REST API docs

Everything above applies if you build your own instead — the source is official, open, and genuinely pleasant to work with once you know the traps.