BORME API clients for Python and TypeScript
Two client libraries for BORME API, an independent service providing data derived from BORME notices and other public registries through a REST API. Both packages are called bormeapi, are released under the MIT licence, and start at version 1.0.0.
- Python:
pip install bormeapi. Standard library only, no runtime dependencies, Python 3.9 or later. bormeapi on PyPI - TypeScript and JavaScript:
npm install bormeapi. No runtime dependencies: it uses the globalfetch(Node 18+, Deno, Bun, browsers). Ships ESM and CommonJS builds with type declarations. bormeapi on npm
Python quick start · TypeScript quick start · Your API key · What the clients cover · Pagination · Errors and retries · API reference
Python quick start
pip install bormeapi
from bormeapi import BormeClient
client = BormeClient("YOUR_API_KEY") # or BormeClient() with BORME_API_KEY set
company = client.company_by_nif("A46103834")
print(company["company_name"], company["status"])
for event in client.iter_events(province="MADRID", act_type="INCORPORATION", max_items=20):
print(event["date"], event["company_name"])
Pass your key explicitly, or use client = BormeClient() after setting BORME_API_KEY: the variable is read only when no key is passed.
TypeScript quick start
npm install bormeapi
import { BormeClient } from "bormeapi";
const client = new BormeClient({ apiKey: process.env.BORME_API_KEY });
const company = await client.companyByNif("A46103834");
console.log(company.company_name, company.status);
for await (const ev of client.iterEvents({ province: "MADRID", act_type: "INCORPORATION" }, { maxItems: 20 }))
console.log(ev.date, ev.company_name);
CommonJS works too: const { BormeClient } = require("bormeapi");
Your API key
Get a free key on your account page. The free plan allows 50 requests a day and provides access to the last 7 days of history; some endpoints and filters require a paid plan (see pricing). The key travels in the X-API-Key header only.
Do not put your key in code that runs in a visitor's browser: anyone could read it there.
What the clients cover
Both clients offer the same operations. Python uses snake_case method names, TypeScript camelCase.
Python
- Companies:
company(slug, as_of=, include_inferred=),company_by_nif(nif),companies_by_nif([...])(up to 100),companies_by_hoja(hoja),search_companies(name, province=, status=, limit=, offset=),company_events(slug, ...),company_signals(slug, ...),company_risk(slug),company_network(slug, ...),person_roles(name). - EU VAT numbers:
vat_check(vat_number). - Events:
events(**filters)for one page,iter_events(**filters)to go through the pages automatically (limits under Pagination),events_csv(**filters)for CSV bytes. - Signals:
signals(signal_type=, date_from=, date_to=, province=, ...). - Trademarks and trading names:
search_brands(q),company_brands(slug). - Watchlists:
create_watchlist(webhook_url=, company_slug= | name_contains= | filter=),list_watchlists(),delete_watchlist(id). - Reference data:
act_types(),provinces(),sources(),health(). - Anything else:
request("GET", "/v1/...", params={...}).
With include_inferred=True, company() and company_events() may also return a CNAE-2009 division inferred by a language model from the company's stated corporate purpose (objeto social), when it meets the confidence threshold (0.9 by default) and no CNAE code appears on the company card (for company()) or in any of the event's acts (for company_events()) (cnae_source jev, details in cnae_inference). It describes the stated purpose, not the company's verified activity, and is not a code published in the BORME. If include_inferred is omitted or set to False, no inferred values are included.
Responses are plain dicts typed with TypedDict (bormeapi.models), so new fields the API adds reach you without an upgrade.
TypeScript
- Companies:
company(slug, { asOf, include_inferred }),companyByNif(nif),companiesByNif([...])(up to 100),companiesByHoja(hoja),searchCompanies(name, { province, status, limit, offset }),companyEvents(slug, ...),companySignals(slug, ...),companyRisk(slug),companyNetwork(slug, ...),personRoles(name). - EU VAT numbers:
vatCheck(vatNumber). - Events:
events(filters)for one page,iterEvents(filters, { pageSize, maxItems })to go through the pages automatically (limits under Pagination),eventsCsv(filters)for CSV text. - Signals:
signals({ signal_type, date_from, date_to, province, ... }). - Trademarks and trading names:
searchBrands(q),companyBrands(slug). - Watchlists:
createWatchlist({ webhook_url, company_slug | name_contains | filter }),listWatchlists(),deleteWatchlist(id). - Reference data:
actTypes(),provinces(),sources(),health(). - Anything else:
request("GET", "/v1/...", { params }).
With { include_inferred: true }, company() and companyEvents() may also return a CNAE-2009 division inferred by a language model from the company's stated corporate purpose (objeto social), when it meets the confidence threshold (0.9 by default) and no CNAE code appears on the company card (for company()) or in any of the event's acts (for companyEvents()) (cnae_source jev, details in cnae_inference). It describes the stated purpose, not the company's verified activity, and is not a code published in the BORME. If include_inferred is omitted or set to false, no inferred values are included.
Response fields keep the API's snake_case names, so the API reference applies as written.
In both clients
- Event filters:
date_from,date_to,province,act_type,company_name_contains,capital_min,capital_max,municipality. - The VAT check tells you whether VIES reported the number as valid for intra-EU transactions on
request_date(Basic plan or higher). - Company responses also include
lei(the GLEIF Legal Entity Identifier found by NIF) andwebsite(the address as published in the BORME); the API reference explains when they are null.
Pagination
iter_events (Python) and iterEvents (TypeScript) request pages of up to 200 events and stop at the last page, after max_items / maxItems, or when the offset would pass 10,000 (the API limit). For larger result sets, split the date range. Every page is one request and counts against your quota.
Errors and retries
HTTP error responses raise (Python) or throw (TypeScript) ApiError or one of its subclasses, with status, code (the API error code when there is one), the message (message in Python, apiMessage in TypeScript), body and headers:
AuthenticationError(401),PermissionDeniedError(403),NotFoundError(404, withsuggestionsfor an unknown slug),ConflictError(409, withcandidatesfor an ambiguous NIF),ValidationError(422, witherrors),RateLimitError(429),ServiceUnavailableError(503),GatewayTimeoutError(504).- Network failures raise or throw
TransportError.
A 429 or 503 is retried up to three times (max_retries=3 in Python, maxRetries: 3 in TypeScript) only when Retry-After is valid and asks for a wait of at most 60 seconds (max_retry_wait=60, maxRetryWaitSeconds: 60); otherwise the error is raised at once. A 503 is retried only for GET and DELETE. A 429 without Retry-After means the daily quota is used up: it resets at 00:00 UTC, so it is raised at once, with limit and upgrade_url (upgradeUrl in TypeScript).
from bormeapi import NotFoundError
try:
client.company("mercadona-sl")
except NotFoundError as e:
print(e.message, e.suggestions)
import { NotFoundError } from "bormeapi";
try {
await client.company("mercadona-sl");
} catch (e) {
if (e instanceof NotFoundError) console.log(e.apiMessage, e.suggestions);
}
Requests using an old slug for a renamed or merged company are redirected to the current slug. The clients follow up to three redirects, only to the same origin, so the key is never sent elsewhere. The TypeScript client can do this when the runtime exposes Location (Node, Deno, Bun); in browsers, an opaque redirect throws TransportError.
API reference
Every field, filter and error code is described in the API reference (in Spanish: documentación de la API). The packages: bormeapi on PyPI · bormeapi on npm.
Questions about an integration? Write to us; we answer personally, usually the same working day.