# registry-mcp — the company registry MCP: full reference for LLM readers

Company data for AI agents, any country. One shape, many national registries.
This document is written for a model deciding which call to make. Every section
says what the call is for, when to use it, what comes back, and what to do on
each error.

Base URL: https://api.foretak.dev
MCP endpoint (Streamable HTTP): https://api.foretak.dev/mcp
Local stdio: `uvx registry-mcp` or `npx registry-mcp`
Source (MIT): https://github.com/foretak/registry-mcp
Short version of this file: https://api.foretak.dev/llms.txt

Aliases you may be searching for: brreg, Brønnøysund, Brønnøysundregistrene,
Enhetsregisteret, organisasjonsnummer, orgnr, org.nr, Norway company lookup,
Norwegian business registry, foretak, Companies House, Companies House API,
company number, company registration number, CRN, UK company lookup, UK company
search, confirmation statement, Bolagsverket, organisationsnummer, svenskt
företag, företagsinformation, kontrollera företag, årsredovisning, F-skatt,
Swedish company lookup, Swedish company number, company registry,
company registry MCP, MCP.


## 0. The one thing to know first

REST and MCP return the **same JSON documents**. A `CompanyReport` from
`GET /v1/NO/company/923609016` is byte-identical to the one from the MCP tool
`lookup_company("923609016", country="NO")`. Learn the shape once.

Unknown is always `null` — never `""`, never `0`. `"employees": null` means the
register does not publish a figure; `"employees": 0` means it publishes zero.
`employees_reported` tells the two apart explicitly.

Dates are ISO-8601 `YYYY-MM-DD`. Timestamps are timezone-aware UTC.
Country codes are ISO-3166-1 alpha-2, upper-case.


## 1. Countries currently supported

| Country | Code | Register | Identifier | Example |
|---|---|---|---|---|
| Norway | `NO` | Enhetsregisteret (Brønnøysundregistrene), slug `brreg` | organisasjonsnummer (orgnr) | `923609016` |
| United Kingdom | `GB` | Companies House, slug `companies-house` | company number (company registration number, CRN) | `00445790` |
| Sweden | `SE` | Bolagsverket (with Statistics Sweden/SCB in the same payload), slug `bolagsverket` | organisationsnummer — ten digits; a sole trader's is their twelve-digit personnummer | `5560160680` |

Do not hard-code this table — call `list_countries()` / `GET /v1/countries`.
Countries are added as separate modules; the list grows without the tool shape
changing.

**The United Kingdom is `GB`, never `UK`.** Country codes are ISO-3166-1
alpha-2 with no alias table, so `GB` is the only code that routes; `UK` returns
`404 unsupported_country` with a hint naming the supported codes. Write "UK" to
a user, send `GB` to this API.

**Companies House needs a credential.** `GB` is the first module whose upstream
requires an API key, so every `CountryInfo` row carries `requires_api_key` and
`api_key_env`. On the hosted service at api.foretak.dev the key is configured
and `GB` answers. On a self-hosted deployment with no
`COMPANIES_HOUSE_API_KEY`, `GB` calls return `502 upstream_error` naming that
variable while every other country keeps answering — the flag tells you a key
is *needed*, not whether this deployment *has* one, so read the error when one
arrives rather than pre-judging from the flag.

**Bolagsverket needs a credential too**, an OAuth 2 client pair rather than a
key: `BOLAGSVERKET_CLIENT_ID` and `BOLAGSVERKET_CLIENT_SECRET`, issued on
request. `requires_api_key` is `true` for `SE` and `api_key_env` names the
first of the pair. The hosted service has them configured and `SE` answers.

**Sweden has no name search, permanently.** Bolagsverket's free "värdefulla
datamängder" API has four operations and none of them takes a company name, so
`search_company` for `SE` returns `501 not_implemented` with a hint naming the
alternatives. This is a fact about the register, not a gap in this module: look
a Swedish company up by its organisationsnummer, or use Bolagsverket's bulk
downloadable files if you genuinely need a name index.

**Sweden publishes no status field.** Norway publishes four lifecycle flags and
this service derives `active` from the absence of all four; the UK publishes one
status string and it is mapped one-to-one; Sweden publishes neither, and
`status` is derived from three independent signals — a strike-off date, any
ongoing winding-up or restructuring procedure, and Statistics Sweden's
"economically active" marking. The consequence is worth carrying into your
answer: **for `SE`, `is_active: true` means "on the register and not winding
down", which is not the same as trading.** A dormant but perfectly registered
Swedish company is `active` with a `notes` entry saying Statistics Sweden does
not mark it as economically active. Read `status_detail` and `notes`, not just
the boolean.


## 2. Choosing a call

- You have a national identifier and want facts  → `lookup_company`
- You have only a company name                    → `search_company`, then
  `lookup_company` with the `id` of the right hit. Not available for `SE`:
  Bolagsverket's free API has no name index, so `SE` needs an identifier
- You want to know if an identifier is well-formed, with no network round-trip
  → `validate_company_id`
- You want filing dates for a company             → `company_deadlines`
- You want to know which countries work           → `list_countries`

Never guess an identifier. If `search_company` returns several hits, show the
user the candidates with their `confidence` and `confidence_basis` rather than
picking one silently.

Pick the country code before the call, not after: `NO` for a Norwegian
organisasjonsnummer (nine digits), `GB` for a UK company number (eight
characters, digits or two letters and six digits), `SE` for a Swedish
organisationsnummer (ten digits, written `556016-0680`). A user saying "this UK
supplier" means `country="GB"`.

Nine digits, ten digits, eight characters — the lengths are the fastest
discriminator when a user pastes a bare number with no country. But do not rely
on length alone across the two Nordic spellings: Norwegian
*organisasjonsnummer* and Swedish *organisationsnummer* differ by one letter and
mean different registers. If the user's text is ambiguous, ask, or call
`validate_company_id` in each candidate country first — it costs no network
round trip.


## 3. REST endpoints

### 3.1 GET /v1/countries

**For:** discovering which national registries this service can answer for, and
what each one's identifier looks like.
**When:** before your first call in a new country, or when a user names a
country you have not used here before.

    curl https://api.foretak.dev/v1/countries

Response — a `CountriesResponse` document, one `CountryInfo` row per registry
that can answer right now, sorted by country code. This is the complete
response today, not an excerpt:

    {
      "countries": [
        {
          "country": "GB",
          "registry": "companies-house",
          "name": "Companies House (United Kingdom)",
          "id_scheme": "company number",
          "id_example": "00445790",
          "id_description": "A UK company registration number (CRN): 8 characters, either 8 digits or a two-letter prefix and 6 digits. Shorter numbers are zero-padded, so 445790 is written 00445790. There is no check digit.",
          "source_url": "https://api.company-information.service.gov.uk",
          "license": "Crown copyright — Companies House public register, free to re-use",
          "is_stub": false,
          "requires_api_key": true,
          "api_key_env": "COMPANIES_HOUSE_API_KEY"
        },
        {
          "country": "NO",
          "registry": "brreg",
          "name": "Enhetsregisteret (Brønnøysundregistrene)",
          "id_scheme": "organisasjonsnummer",
          "id_example": "923609016",
          "id_description": "A Norwegian organisasjonsnummer (orgnr): nine digits, the ninth a MOD11 check digit. Written '923 609 016' or '923609016'; a VAT number adds 'MVA'.",
          "source_url": "https://data.brreg.no/enhetsregisteret/api",
          "license": "NLOD 2.0",
          "is_stub": false,
          "requires_api_key": false,
          "api_key_env": null
        },
        {
          "country": "SE",
          "registry": "bolagsverket",
          "name": "Bolagsverket (Sweden)",
          "id_scheme": "organisationsnummer",
          "id_example": "5560160680",
          "id_description": "A Swedish organisationsnummer: ten digits, written 556016-0680, with a check digit. A sole trader is looked up by a twelve-digit personnummer instead (YYYYMMDDNNNN), and one such number can carry several registered businesses.",
          "source_url": "https://gw.api.bolagsverket.se/vardefulla-datamangder/v1",
          "license": "Free re-use (Bolagsverket/SCB high-value datasets, EU Open Data Directive) — the publisher names no licence",
          "is_stub": false,
          "requires_api_key": true,
          "api_key_env": "BOLAGSVERKET_CLIENT_ID"
        }
      ]
    }

`id_example` is a real, valid identifier — use it to smoke-test the service in
a country you have not called before, rather than guessing one. `is_stub` marks
example/template modules, which are hidden from this list unless stubs are
explicitly requested; a row you can see is a registry that answers.

`requires_api_key` says the *upstream register* needs a credential, and
`api_key_env` names the environment variable that carries it (`null` when none
is needed). It is a declaration about the register, never a health check on
this deployment: a row with `requires_api_key: true` is still listed whether or
not the key is set here. If a `GB` call then fails with `502 upstream_error`,
its `hint` names `COMPANIES_HOUSE_API_KEY` and where to get a free one.

MCP `list_countries` returns this identical document, both keys included.

Errors: none expected. A 500 means a bug here — retry once, then tell the user.

### 3.2 GET /v1/{country}/company/{id}

**For:** the full report on one registered entity.
**When:** you already have the national identifier — from the user, an invoice,
a contract, or a previous `search_company` hit.

    curl https://api.foretak.dev/v1/NO/company/923609016

Response (abridged — every field is documented in §5):

    {
      "country": "NO",
      "registry": "brreg",
      "id": "923609016",
      "id_formatted": "923 609 016",
      "id_scheme": "organisasjonsnummer",
      "euid": null,
      "name": "EQUINOR ASA",
      "previous_names": ["STATOIL ASA", "STATOILHYDRO ASA", "STATOIL ASA",
                         "Den norske stats oljeselskap a.s"],
      "legal_form_code": "ASA",
      "legal_form": "Public limited company",
      "legal_form_local": "Allmennaksjeselskap",
      "limited_liability": true,
      "has_board_duty": true,
      "has_annual_accounts_duty": true,
      "status": "active",
      "status_detail": "Registered and active in Enhetsregisteret.",
      "is_active": true,
      "registered_at": "1995-03-12",
      "founded_at": "1972-09-18",
      "business_register_registered_at": "1988-04-28",
      "vat_registered": true,
      "vat_registered_at": "1989-07-01",
      "vat_number": "NO923609016MVA",
      "in_business_register": true,
      "registers": {
        "foretaksregisteret": true, "stiftelsesregisteret": false,
        "frivillighetsregisteret": false, "partiregisteret": false,
        "mvaregisteret": true
      },
      "employees": 21239,
      "employees_reported": true,
      "industry_codes": [
        {"code": "06.100", "description": "Utvinning av råolje",
         "scheme": "NACE", "rank": 1},
        {"code": "06.200", "description": "Utvinning av naturgass",
         "scheme": "NACE", "rank": 2},
        {"code": "19.200", "scheme": "NACE", "rank": 3,
         "description": "Produksjon av raffinerte petroleumsprodukter og fossile brenselsprodukter"}
      ],
      "sector_code": "1120",
      "sector": "Statlig eide aksjeselskaper mv.",
      "share_capital": 5976872600.0,
      "share_capital_currency": "NOK",
      "business_address": {
        "lines": ["Forusbeen 50"], "postal_code": "4035",
        "city": "STAVANGER", "municipality": "STAVANGER",
        "municipality_code": "1103", "country_code": "NO",
        "country_name": "Norge"
      },
      "website": "www.equinor.com",
      "email": null,
      "phone": "51 99 00 00",
      "advertising_protected": null,
      "parent_id": null,
      "is_subunit": false,
      "in_group": true,
      "last_annual_accounts_year": 2025,
      "confidence": 1.0,
      "confidence_basis": "exact identifier lookup in Enhetsregisteret",
      "cached": false,
      "fetched_at": "2026-09-03T09:12:44Z",
      "source": "Enhetsregisteret (Brønnøysundregistrene)",
      "source_url": "https://data.brreg.no/enhetsregisteret/api/enheter/923609016",
      "license": "NLOD 2.0",
      "notes": []
    }

Identifier input is normalised for you: `923 609 016`, `923.609.016`,
`NO923609016MVA` and `NO 923 609 016 MVA` all resolve to `923609016`.

The same call for the United Kingdom, and the same document shape:

    curl https://api.foretak.dev/v1/GB/company/00445790

Response (abridged the same way; MCP
`lookup_company("00445790", country="GB")` returns this identical document):

    {
      "country": "GB",
      "registry": "companies-house",
      "id": "00445790",
      "id_formatted": null,
      "id_scheme": "company number",
      "euid": null,
      "name": "TESCO PLC",
      "previous_names": ["TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY",
                         "TESCO STORES (HOLDINGS) LIMITED"],
      "legal_form_code": "plc",
      "legal_form": "Public limited company",
      "legal_form_local": "Public limited company",
      "limited_liability": true,
      "has_board_duty": true,
      "has_annual_accounts_duty": true,
      "status": "active",
      "status_detail": "Active on the Companies House register.",
      "is_active": true,
      "registered_at": "1947-11-27",
      "founded_at": "1947-11-27",
      "vat_registered": null,
      "vat_number": null,
      "registers": {"charges": false, "insolvency": false},
      "employees": null,
      "employees_reported": false,
      "industry_codes": [
        {"code": "47110", "description": null, "scheme": "SIC 2007", "rank": 1}
      ],
      "share_capital": null,
      "business_address": {
        "lines": ["Tesco House, Shire Park", "Kestrel Way"],
        "postal_code": "AL7 1GA", "city": "Welwyn Garden City",
        "municipality": null, "municipality_code": null,
        "country_code": "GB", "country_name": "United Kingdom"
      },
      "advertising_protected": null,
      "last_annual_accounts_year": 2026,
      "published_deadlines": [
        {"kind": "annual_accounts", "due_date": "2027-08-26",
         "period_start": "2026-03-01", "period_end": "2027-02-26",
         "overdue": false, "source": "accounts.next_accounts.due_on"},
        {"kind": "confirmation_statement", "due_date": "2027-07-02",
         "period_start": null, "period_end": "2027-06-18",
         "overdue": false, "source": "confirmation_statement.next_due"}
      ],
      "confidence": 1.0,
      "confidence_basis": "exact identifier lookup in the Companies House register",
      "cached": false,
      "fetched_at": "2026-09-04T16:56:06Z",
      "source": "Companies House (UK)",
      "source_url": "https://find-and-update.company-information.service.gov.uk/company/00445790",
      "license": "Crown copyright — Companies House public register, free to re-use",
      "notes": []
    }

UK identifier input is normalised too, but differently: a short number is
zero-padded and a letter prefix upper-cased, so `445790` → `00445790` and
`oc303675` → `OC303675`. `id_formatted` is `null` because UK company numbers
have no conventional grouping — that is honesty, not a missing feature.

**What Companies House does not publish, and therefore returns as `null`:**
`vat_registered`, `vat_registered_at` and `vat_number` (UK VAT is HMRC's
separate register, and a VAT number is not derivable from a company number);
`employees` — the UK register carries no employee count at all, for any
company, so `employees_reported` is always `false` for `GB`; `share_capital`
(it lives in the confirmation statement, not the profile); `purpose`,
`website`, `email`, `phone`, `sector`. Norway publishes most of these, the UK
publishes none of them: same shape, different coverage. Do not read a `null`
here as "this company has no employees" or "not VAT-registered" — read it as
"this register does not say".

`industry_codes` for `GB` are SIC 2007 codes with `description: null`;
Companies House returns the code only. `registers` for `GB` has two boolean
keys, `charges` and `insolvency`, from the register's own `has_charges` and
`has_insolvency_history` flags. `source_url` points at the human-readable
Companies House record, which is the link to hand a user.

Errors:
- `400 invalid_id` — the identifier fails the country's checksum or format.
  Do **not** retry the same string. Either fix the digits or call
  `search_company` with the name.
- `404 not_found` — well-formed but no such entity. The number may never have
  been issued, or the entity was deleted. Call `search_company` with the name.
- `404 unsupported_country` — no module for that country code. Call
  `list_countries` and pick a supported one; do not invent a route.
- `502 upstream_error` / `504 upstream_timeout` — the national register is
  down or slow. We already retried once. Wait ~60 s and retry at most once,
  then tell the user the register is unavailable. Do not loop.
- `429 rate_limited` — you exceeded 60 requests/minute per IP. Back off for the
  number of seconds in `details.retry_after` and batch your calls.

Two of these read differently for `GB`. A `404 not_found` hint adds that **sole
traders and ordinary partnerships are not registered at Companies House at
all** — a UK business can be entirely real and never appear here, which is not
true of Norway, where a sole proprietorship (ENK) has an organisasjonsnummer.
And `502 upstream_error` on `GB` has a second cause besides an upstream
outage: no Companies House API key configured on this deployment. Its `message`
says so and its `hint` names `COMPANIES_HOUSE_API_KEY`; that one is not fixed
by retrying, so tell the user rather than looping.

The same call for Sweden, and the same document shape again — this is the live
0.3.0 response, abridged to the fields that differ:

    curl https://api.foretak.dev/v1/SE/company/5560160680

    {
      "country": "SE",
      "registry": "bolagsverket",
      "id": "5560160680",
      "id_formatted": "556016-0680",
      "id_scheme": "organisationsnummer",
      "euid": null,
      "name": "Telefonaktiebolaget LM Ericsson",
      "legal_form_code": "AB",
      "legal_form": "Private or public limited company",
      "legal_form_local": "Aktiebolag",
      "limited_liability": true,
      "has_board_duty": true,
      "has_annual_accounts_duty": true,
      "status": "active",
      "status_detail": "Registered with Bolagsverket and not marked as struck off or in any winding-up or restructuring procedure.",
      "is_active": true,
      "registered_at": "1918-08-19",
      "vat_registered": null,
      "employees": null,
      "employees_reported": false,
      "industry_codes": [
        {"code": "70100", "description": "Verksamheter som utövas av huvudkontor", "scheme": "SNI 2007", "rank": 1}
      ],
      "postal_address": {"lines": [], "postal_code": "16483", "city": "STOCKHOLM", "country_code": "SE"},
      "advertising_protected": null,
      "published_deadlines": [],
      "confidence": 1.0,
      "confidence_basis": "exact identifier lookup in the Bolagsverket register",
      "source": "Bolagsverket (bolagsverket.se)",
      "source_url": "https://gw.api.bolagsverket.se/vardefulla-datamangder/v1",
      "license": "Free re-use (Bolagsverket/SCB high-value datasets, EU Open Data Directive) — the publisher names no licence",
      "notes": ["Filing deadlines are computed assuming a financial year ending 31 December. …"]
    }

Four things about `SE` specifically.

**`status` was derived, not read.** See §1: Bolagsverket publishes no status
field. `status_detail` is the sentence that says which of the three signals
decided it, and it is the field to quote. `is_active` for `SE` means *on the
register and not winding down* — a company can be `active` here and not be
trading, and when Statistics Sweden does not mark it economically active a
`notes` entry says exactly that, in English.

**`license` names no licence, deliberately.** Bolagsverket publishes this data
free under the EU high-value-datasets regime and names no licence for it, so
the string says what the permission is and says plainly that there is no licence
name to quote. A familiar name in that field would be a fabrication.

**What Bolagsverket's free dataset does not publish, and therefore returns as
`null`:** `vat_registered` and everything VAT (Skatteverket's register, not
Bolagsverket's); `employees`, so `employees_reported` is always `false` for
`SE`; `share_capital`; officers and beneficial owners; any financial figure;
the financial-year end; a visiting address, `email`, `phone` and `website`.
`business_address` is `null` and `postal_address` carries what there is.

**`published_deadlines` is `[]`.** Sweden publishes no per-company filing dates,
so both Swedish deadlines are computed from statute — see §3.4.

### 3.3 GET /v1/{country}/search?q={name}

**For:** turning a company name into candidate identifiers.
**When:** the user gave you a name, not a number.

    curl "https://api.foretak.dev/v1/NO/search?q=equinor&limit=5"

`limit` is 1–100, default 10. A `limit` outside that range is a
`400 bad_request`, not a silent clamp.

Response:

    {
      "country": "NO",
      "registry": "brreg",
      "query": "equinor",
      "hits": [
        {
          "country": "NO", "registry": "brreg",
          "id": "923609016", "name": "EQUINOR ASA",
          "legal_form_code": "ASA", "legal_form": "Public limited company",
          "status": "active", "city": "STAVANGER",
          "municipality": "STAVANGER", "registered_at": "1995-03-12",
          "is_subunit": false,
          "confidence": 0.8,
          "confidence_basis": "search hit name starts with the query",
          "source_url": "https://data.brreg.no/enhetsregisteret/api/enheter/923609016"
        }
      ],
      "total": 240,
      "truncated": true,
      "cached": false,
      "fetched_at": "2026-09-03T09:13:02Z",
      "hint": "240 companies match. Call lookup_company with the id of the right hit for the full report."
    }

A search hit is deliberately thin. It is enough to **choose**, not enough to
act on. Call `lookup_company` before telling a user anything about VAT, status
or deadlines.

Confidence anchors: `0.95` exact case-insensitive name match, `0.8` the name
starts with the query, `0.6` the name contains every query token, `0.4` any
other hit the register returned. The hit above scores `0.8` rather than `0.95`
because `equinor` is a *prefix* of `EQUINOR ASA`, not the whole name; searching
`equinor asa` returns the same company at `0.95`. Do not read `0.8` as doubt
about which company it is.

Errors:
- `400 bad_request` — `q` missing/empty, or `limit` outside 1–100. Fix the
  parameter and call again.
- `404 unsupported_country`, `429`, `502`, `504` — as in §3.2.
- Zero hits is **not** an error: `hits: []`, `total: 0`, and a `hint` telling
  you to try a shorter or differently spelled name. Norwegian names are
  registered upper-case and often contain `AS`, `ASA` or `NUF` — try dropping
  the suffix before concluding a company does not exist.

For the United Kingdom:

    curl "https://api.foretak.dev/v1/GB/search?q=tesco&limit=3"

    {
      "country": "GB",
      "registry": "companies-house",
      "query": "tesco",
      "hits": [
        {
          "country": "GB", "registry": "companies-house",
          "id": "00445790", "name": "TESCO PLC",
          "legal_form_code": "plc", "legal_form": "Public limited company",
          "status": "active", "city": "Welwyn Garden City",
          "municipality": null, "registered_at": "1947-11-27",
          "is_subunit": false,
          "confidence": 0.8,
          "confidence_basis": "search hit title starts with the query",
          "source_url": "https://find-and-update.company-information.service.gov.uk/company/00445790"
        }
      ],
      "total": 356,
      "truncated": true,
      "cached": false,
      "fetched_at": "2026-09-04T16:58:41Z",
      "hint": "356 companies match. Call lookup_company with the id of the right hit for the full report."
    }

Two things to know about `GB` search specifically. **Hits come back in the
register's own relevance order, not sorted by `confidence`** — the real
three-hit response above also contained an unrelated `ltd` at `0.4` ranked
above a `0.8` hit. Read every hit's `confidence` and `confidence_basis`; do not
assume the first row is the best one. And **a UK search result is the only
place `status` can be missing upstream** — those come back as
`"status": "unknown"` rather than a missing key, so branch on the value, not on
the key's presence.

Zero hits for `GB` says the same thing in UK terms: sole traders and ordinary
partnerships are not on the Companies House register, so no spelling of the
name will find them. UK names usually end in `LIMITED`, `LTD`, `PLC` or `LLP` —
drop the suffix before concluding a company does not exist.

**Sweden does not implement this endpoint at all**, and never will on the free
API. This is the whole response:

    curl "https://api.foretak.dev/v1/SE/search?q=ericsson"      # HTTP 501

    {
      "error": {
        "code": "not_implemented",
        "message": "Bolagsverket's free API cannot search by company name.",
        "hint": "Sweden can only be looked up by identifier: call lookup_company with the ten-digit organisationsnummer (e.g. 5560160680), or the twelve-digit personnummer for a sole trader — validate_company_id will check the shape first without spending a lookup. Bolagsverket publishes the whole register as downloadable files for callers who need to search by name. search_company works for the other countries list_countries returns.",
        "country": "SE",
        "registry": "bolagsverket",
        "details": {}
      }
    }

Do not retry it, do not try a different spelling, and do not fall back to
guessing an identifier. If the user has only a Swedish company name, say that
the register offers no name index and ask them for the organisationsnummer —
it is on every Swedish invoice, in the footer of most Swedish websites, and in
the company's own annual report.

### 3.4 GET /v1/{country}/company/{id}/deadlines?today=YYYY-MM-DD

**For:** the next occurrence of each statutory filing obligation.
**When:** the user asks what a company must file, or by when.

    curl "https://api.foretak.dev/v1/NO/company/923609016/deadlines?today=2026-01-15"

`today` is optional and defaults to the server's current UTC date. Pass it
explicitly whenever you want a reproducible answer, or when reasoning about a
date that is not today.

Response — a `DeadlineReport` document, never a bare list:

    {
      "country": "NO",
      "registry": "brreg",
      "company_id": "923609016",
      "company_name": "EQUINOR ASA",
      "today": "2026-01-15",
      "deadlines": [
        {
          "country": "NO", "registry": "brreg",
          "kind": "shareholder_register_statement",
          "name": "Shareholder register statement",
          "local_name": "Aksjonærregisteroppgaven (RF-1086)",
          "authority": "Skatteetaten",
          "statutory_date": "2026-01-31",
          "due_date": "2026-02-02",
          "rolled_forward": true,
          "period_label": "2025",
          "period_start": "2025-01-01",
          "period_end": "2025-12-31",
          "recurrence": "annual",
          "mandatory": true,
          "applies_because": "An ASA company must file the shareholder register statement (RF-1086) with Skatteetaten (skatteforvaltningsforskriften § 7-7-4(1)).",
          "days_until": 18,
          "source_url": null
        }
      ],
      "notes": ["Filing deadlines are computed assuming a calendar-year accounting period. Enhetsregisteret does not publish a company's accounting year. For a financial year ending between 1 January and 30 June, regnskapsloven § 8-3(1) sets a different deadline — 1 February, not 31 July — so a deviating year changes which rule applies, not just the date. The Ministry may also postpone the accounts deadline by up to one month by regulation (§ 8-3(1)). Verify against Regnskapsregisteret before relying on an annual date."]
    }

The `deadlines` array is truncated to one entry above. The real call for
923609016 on 2026-01-15 returns six, sorted by `due_date`:
`shareholder_register_statement` (2026-02-02), `payroll_report` (2026-02-05),
`vat_return` (2026-02-10), `tax_return` (2026-06-01), `general_meeting`
(2026-06-30), `annual_accounts` (2026-07-31).

MCP `company_deadlines` returns this identical document — same keys, same
`company_id`/`company_name`/`today`/`notes` envelope. Do not expect a bare
array on either surface.

How to read it:
- `statutory_date` is the date in the statute. `due_date` is the date to act
  on. Quote `due_date`.
- `rolled_forward: true` means the two differ because this deadline's own
  legal source rolls a weekend/holiday date to the next working day.
  Roll-forward is decided per deadline, not as a blanket rule: four of the
  six Norwegian deadlines roll (`tax_return`, `shareholder_register_statement`,
  `vat_return` under skatteforvaltningsloven § 5-5 / domstolloven § 149,
  `payroll_report` under a-opplysningsforskriften § 2-1); `annual_accounts`
  and `general_meeting` never do, because rolling would land past the actual
  legal deadline (regnskapsloven § 8-3(1)'s "before 1 August", aksjeloven
  § 5-5(1)'s six-month outer limit) — `rolled_forward` is always `false` for
  those two.
- One entry per `kind`, always the **next** occurrence, sorted by `due_date`.
- `days_until` is negative if the date has passed relative to `today`.
- `applies_because` names the provision the date comes from and any
  assumption behind it. Quote it rather than presenting a date as
  unconditional fact.
- An empty list is a real answer: bankrupt, deleted and compulsorily liquidated
  entities have no filing deadlines, and neither do branches/sub-units. `notes`
  says why, and for a sub-unit points you at `parent_id`.

Norwegian obligations covered: `annual_accounts` (Årsregnskap),
`general_meeting` (Ordinær generalforsamling), `tax_return` (Skattemelding),
`shareholder_register_statement` (Aksjonærregisteroppgaven RF-1086),
`vat_return` (Mva-melding, bimonthly), `payroll_report` (A-melding, monthly).
Advance tax (forskuddsskatt) is deliberately not covered yet — do not infer it.

**The United Kingdom.** Two obligations, both at Companies House:
`annual_accounts` (the annual accounts filing) and `confirmation_statement`
(the CS01). Same envelope, same keys:

    curl "https://api.foretak.dev/v1/GB/company/00445790/deadlines?today=2026-09-04"

    {
      "country": "GB",
      "registry": "companies-house",
      "company_id": "00445790",
      "company_name": "TESCO PLC",
      "today": "2026-09-04",
      "deadlines": [
        {
          "country": "GB", "registry": "companies-house",
          "kind": "confirmation_statement",
          "name": "Confirmation statement",
          "local_name": "Confirmation statement (CS01)",
          "authority": "Companies House",
          "statutory_date": "2027-07-02",
          "due_date": "2027-07-02",
          "rolled_forward": false,
          "period_label": "review period ending 2027-06-18",
          "period_start": "2026-06-19",
          "period_end": "2027-06-18",
          "recurrence": "annual",
          "mandatory": true,
          "applies_because": "Companies House publishes this date for the company itself; it is the register's own figure, not a calculation.",
          "days_until": 301,
          "source_url": "https://www.gov.uk/guidance/confirmation-statement-guidance"
        },
        {
          "country": "GB", "registry": "companies-house",
          "kind": "annual_accounts",
          "name": "Annual accounts filing",
          "local_name": "Annual accounts",
          "authority": "Companies House",
          "statutory_date": "2027-08-26",
          "due_date": "2027-08-26",
          "rolled_forward": false,
          "period_label": "period ending 2027-02-26",
          "period_start": "2026-03-01",
          "period_end": "2027-02-26",
          "recurrence": "annual",
          "mandatory": true,
          "applies_because": "Companies House publishes this date for the company itself; it is the register's own figure, not a calculation.",
          "days_until": 356,
          "source_url": "https://www.gov.uk/government/publications/life-of-a-company-annual-requirements/life-of-a-company-part-1-accounts"
        }
      ],
      "notes": []
    }

MCP `company_deadlines("00445790", country="GB", today="2026-09-04")` returns
this identical document.

Four differences from Norway, all deliberate:

- **Quoted, not computed, wherever possible.** Companies House publishes each
  company's own due dates, and those are taken verbatim. `applies_because`
  tells you which you got: *"the register's own figure, not a calculation"*, or
  a sentence naming the statutory period this tool applied instead (9 months
  after the accounting reference date for a private company, 6 for a public
  one, 14 days after the review period for the confirmation statement). Quote
  it. A quoted date already accounts for shortened, extended and amended
  accounting periods that the profile does not otherwise expose.
- **Nothing rolls forward.** A UK filing deadline that lands on a Sunday or a
  bank holiday is still that date in law, so `due_date == statutory_date` and
  `rolled_forward` is always `false` for `GB`. There is no UK holiday table,
  by design.
- **`days_until` goes negative, routinely.** Companies House leaves an overdue
  due date in the past rather than rolling it to the next cycle, so a live
  active company can show `"days_until": -21` on its confirmation statement.
  That is an overdue filing, not a stale response — say so to the user.
- **Deadlines are emitted only when `status` is `"active"`.** Stricter than
  Norway, where a company under liquidation keeps its list: the UK register
  publishes one status that cannot distinguish voluntary from compulsory
  liquidation, so nothing is guessed. A dissolved, liquidating, administered
  or voluntary-arrangement company returns `"deadlines": []` with a `notes`
  entry explaining that what remains to be filed is the insolvency
  practitioner's or the registrar's to decide.

**Sweden computes both of its deadlines**, because Bolagsverket publishes none
per company. Live, asked on 2026-09-07:

    curl "https://api.foretak.dev/v1/SE/company/5560160680/deadlines?today=2026-09-07"

    {
      "country": "SE",
      "registry": "bolagsverket",
      "company_name": "Telefonaktiebolaget LM Ericsson",
      "today": "2026-09-07",
      "deadlines": [
        {
          "kind": "general_meeting",
          "name": "Ordinary general meeting",
          "local_name": "Ordinarie bolagsstämma (årsstämma)",
          "authority": "Company shareholders (no external filing)",
          "statutory_date": "2027-06-30", "due_date": "2027-06-30",
          "rolled_forward": false, "period_label": "2026",
          "period_start": "2026-01-01", "period_end": "2026-12-31",
          "days_until": 296,
          "applies_because": "An aktiebolag must hold its ordinary general meeting (årsstämma) within six months of the end of each financial year (aktiebolagslagen 7 kap. 10 §). …",
          "source_url": "https://lagen.nu/2005:551"
        },
        {
          "kind": "annual_accounts",
          "name": "Annual accounts filing",
          "local_name": "Årsredovisning",
          "authority": "Bolagsverket",
          "statutory_date": "2027-07-31", "due_date": "2027-07-31",
          "rolled_forward": false, "period_label": "2026",
          "days_until": 327,
          "applies_because": "… a late fee of 7 500 kr (15 000 kr for a public company) starts if the documents have not arrived within seven months of the financial year end (årsredovisningslagen 8 kap. 6 §). …",
          "source_url": "https://lagen.nu/1995:1554"
        }
      ],
      "notes": ["Filing deadlines are computed assuming a financial year ending 31 December. …"]
    }

Three things to carry into your answer:

- **The financial year is assumed, not known.** Bolagsverket's free dataset
  does not publish a company's financial year, and a Swedish financial year
  need not be the calendar year. Both dates assume 31 December, the `notes`
  entry says so, and it also says how to shift them: a 30 June year end gives
  31 December for the meeting and 31 January for the filing. If the user knows
  their year end and it is not December, move both dates by the same number of
  months yourself and tell them you did.
- **The filing date is an outer limit, not this company's own.**
  Årsredovisningslagen 8 kap. 3 § requires filing within one month of the
  general meeting that adopts the accounts; Bolagsverket does not publish the
  meeting date, so the seven-month fee threshold of 8 kap. 6 § is emitted
  instead. A company whose meeting was earlier must file earlier.
- **Nothing rolls forward, and for a different reason than the UK's.** For
  `GB` there is a rule saying a weekend deadline stays put. For `SE` there is
  no sourced rule either way, so `rolled_forward` is `false` because nothing
  was found saying the date moves — not because something was found saying it
  does not. `registry://rules/SE` states that distinction; it matters if a
  Swedish deadline lands on a Sunday.

Skatteverket's deadlines — inkomstdeklaration 2, moms, arbetsgivardeklaration —
are real Swedish obligations that this endpoint does **not** compute, for the
same reason as UK corporation tax: the inputs (the financial-year end, the VAT
period) are not in Bolagsverket's dataset. Do not infer them.

Corporation tax is **not** included for `GB`, and neither is the first-accounts
deadline after incorporation. Both rules are real and both are stated in
`registry://rules/GB`, but the inputs they need — HMRC's accounting period,
whether the entity is within the charge to corporation tax, whether it pays by
quarterly instalment — are not on the Companies House register. Do not infer
them from what this endpoint returns. An LLP is tax-transparent and files no
CT600 at all.

Errors: `400 invalid_id`, `400 bad_request` (unparseable `today`),
`404 not_found`, `404 unsupported_country`, `429`, `502`, `504` — handle as in
§3.2. For `bad_request` on `today`, send `YYYY-MM-DD` and retry once.

### 3.5 GET /v1/{country}/validate/{id}

**For:** checking an identifier's format and checksum with no call to the
national register.
**When:** validating user input or a spreadsheet column, before spending
lookups. Cheap and instant — prefer it to a speculative lookup.

    curl https://api.foretak.dev/v1/NO/validate/923609016

Response — a `ValidationResult` document:

    {
      "country": "NO",
      "registry": "brreg",
      "id_scheme": "organisasjonsnummer",
      "input": "923 609 016",
      "valid": true,
      "normalized": "923609016",
      "formatted": "923 609 016",
      "reason": "Well-formed organisasjonsnummer for NO. A valid identifier does not mean the entity exists — call lookup_company (MCP) or GET /v1/{country}/company/{id} (REST) to find out.",
      "hint": null
    }

Note the key is `normalized`, spelled with a `z`, in every response on both
surfaces. `input` is echoed back exactly as you sent it, punctuation and all;
`normalized` is the digits-only form to feed to a lookup, and `formatted` is
the country's own typography (Norway groups the nine digits in threes).
`formatted` is `null` for a country with no such convention.

An invalid identifier returns `200` with `"valid": false` — this endpoint
answers a question rather than failing, and is the one deliberate exception to
the raise-on-failure rule in §6. `reason` says what is wrong and `hint` says
what to call instead:

    {
      "country": "NO",
      "registry": "brreg",
      "id_scheme": "organisasjonsnummer",
      "input": "833286602",
      "valid": false,
      "normalized": null,
      "formatted": null,
      "reason": "'833286602' is not a valid Norwegian organisasjonsnummer.",
      "hint": "An organisasjonsnummer is nine digits with a MOD11 check digit, e.g. 923609016. If you have a company name instead, call search_company."
    }

`404 unsupported_country` is the only common error. MCP `validate_company_id`
returns the same document.

Valid format does **not** mean the company exists. `833286602` above is a
well-known example of a string that looks right and is not: it fails MOD11 and
no such entity exists. Follow a successful validation with `lookup_company` if
you need facts.

For the United Kingdom this endpoint is a **normaliser first and a checker
second**, because a UK company number has no check digit:

    curl https://api.foretak.dev/v1/GB/validate/445790

    {
      "country": "GB",
      "registry": "companies-house",
      "id_scheme": "company number",
      "input": "445790",
      "valid": true,
      "normalized": "00445790",
      "formatted": null,
      "reason": "Well-formed company number for GB. A valid identifier does not mean the entity exists — call lookup_company (MCP) or GET /v1/{country}/company/{id} (REST) to find out.",
      "hint": null
    }

MCP `validate_company_id("445790", country="GB")` returns the same document.
Use it exactly as you would for Norway — on a spreadsheet column, before
spending real lookups — but read `valid: true` for `GB` as *the shape is
right*, and nothing more. Norway's MOD11 rejects a transposed digit; the UK has
no such check, so a mistyped company number is well-formed and will simply come
back `404 not_found` from `lookup_company`. `normalized` is the value to feed
onward: `445790` becomes `00445790`, `oc303675` becomes `OC303675`.
`formatted` is `null` because there is no UK grouping convention.

**Sweden is the third variation: a check digit that exists but that this
service does not enforce.** `formatted` groups the ten digits as `556016-0680`,
and `reason` carries the whole position rather than hiding it:

    curl https://api.foretak.dev/v1/SE/validate/5560212524

    {
      "country": "SE",
      "registry": "bolagsverket",
      "id_scheme": "organisationsnummer",
      "input": "5560212524",
      "valid": true,
      "normalized": "5560212524",
      "formatted": "556021-2524",
      "reason": "Well-formed organisationsnummer for SE. A valid identifier does not mean the entity exists — call lookup_company (MCP) or GET /v1/{country}/company/{id} (REST) to find out. Note that this number does not satisfy the modulus-10 check digit that Swedish identifiers are generally described as carrying. registry-mcp has not been able to confirm that rule against a primary source, as of 2026-09, so the number is not rejected here — but Bolagsverket validates a check digit server-side and may answer 'Identitetsbeteckning har ogiltig kontrollsiffra'. Check the digits before relying on it.",
      "hint": null
    }

So for `SE`, `valid: true` can be followed by a `400 invalid_id` from the
register itself — that same number returns

    {"error": {"code": "invalid_id",
      "message": "Bolagsverket rejected 5560212524 as a malformed identitetsbeteckning.",
      "hint": "Bolagsverket validates a check digit that this module does not: it answers 'Identitetsbeteckning har ogiltig kontrollsiffra' for a number of the right length whose check digit is wrong. Check the digits. An organisationsnummer is ten digits and a personnummer is twelve (YYYYMMDDNNNN).",
      "country": "SE", "registry": "bolagsverket", "details": {}}}

Read `reason` for `SE`, not only `valid`. Three countries, three meanings of
the same boolean: Norway's MOD11 is enforced and a failing number never reaches
the register; the UK has no check digit to enforce; Sweden has one that this
module cannot source and therefore defers to Bolagsverket rather than
implementing from memory.

`validate_company_id` for `SE` accepts twelve digits as well as ten — a Swedish
sole trader is looked up by their personnummer, which is that trader's company
number. §7 says what this service does with such an identifier, which is: not
log it.

### 3.6 GET /health

Liveness for monitoring:
`{"status": "ok", "version": "0.3.0", "countries": ["GB", "NO", "SE"]}`. Not part of
the data API; do not call it in a loop — `GET /v1/countries` is the discovery
document, this is only a heartbeat.


## 4. MCP tools

Add the server:

    claude mcp add registry-mcp --transport http https://api.foretak.dev/mcp

or run it locally over stdio with `uvx registry-mcp` / `npx registry-mcp`.

| Tool | Signature | Returns | REST twin |
|---|---|---|---|
| `lookup_company` | `(id: str, country: str = "NO")` | `CompanyReport` | §3.2 |
| `search_company` | `(name: str, country: str = "NO", limit: int = 10)` | `SearchResult` | §3.3 |
| `company_deadlines` | `(id: str, country: str = "NO", today: str \| None = None)` | `DeadlineReport` | §3.4 |
| `validate_company_id` | `(id: str, country: str = "NO")` | `ValidationResult` | §3.5 |
| `list_countries` | `()` | supported countries | §3.1 |

Every tool returns byte-for-byte the same JSON document as its REST twin — one
shape per operation across both surfaces. In particular `company_deadlines`
returns the `DeadlineReport` object of §3.4, not a bare array of deadlines, and
`validate_company_id` returns the `ValidationResult` object of §3.5 with
`valid: false` rather than raising on a bad identifier.

`country` defaults to `"NO"` on every tool that takes it, so a UK or Swedish
call must pass it explicitly — `lookup_company("00445790", country="GB")`,
`search_company("tesco", country="GB")`,
`company_deadlines("00445790", country="GB", today="2026-09-04")`,
`validate_company_id("445790", country="GB")`;
`lookup_company("5560160680", country="SE")`,
`company_deadlines("5560160680", country="SE", today="2026-09-07")`,
`validate_company_id("556016-0680", country="SE")`. The UK value is `"GB"`;
`"UK"` raises `unsupported_country`. `search_company(..., country="SE")` raises
`not_implemented` — Sweden has no name index (§3.3), so it is the one
tool/country pair in the table above that has no answer to give.

Resource `registry://rules/{country}` — the country's identifier rules, legal
forms and deadline rules as a document you can read once and reason with,
instead of calling `validate_company_id` in a loop. Read it before processing a
list of identifiers.

Prompt `explain_company` — takes an identifier and a country and produces a
plain-language summary of a company: what it is, whether it is trading, whether
it is VAT-registered, and what it must file next.

Errors arrive as the same `{"error": {...}}` document described in §6, so the
handling in §3 applies unchanged.


## 5. The CompanyReport shape

Groups, in the order they appear:

- **Identity** — `country`, `registry`, `id`, `id_formatted`, `id_scheme`, `euid`.
- **Names** — `name`, `previous_names` (newest first).
- **Legal form** — `legal_form_code` (national, e.g. `AS`, `ASA`, `ENK`),
  `legal_form` (English label), `legal_form_local`, `limited_liability`,
  `has_board_duty`, `has_annual_accounts_duty`. A duty field is `null` when it
  depends on facts the register does not publish (turnover, balance sheet).
  `null` means "we do not know", never "no".
- **Status** — `status` is one of `active`, `under_liquidation`,
  `under_compulsory_liquidation`, `bankrupt`, `dissolved`, `deleted`,
  `unknown`. `status_detail` is one English sentence naming the flag it came
  from; `is_active` mirrors `status == "active"` so you need no enum table.
  **The enum is shared; the derivation is not.** Norway publishes four
  lifecycle flags (`konkurs`, `underAvvikling`,
  `underTvangsavviklingEllerTvangsopplosning`, `slettedato`) and `active` is
  the absence of all four — with all four missing rather than false mapping to
  `unknown`, because absence is not a negative. The UK publishes one status
  string, mapped one-to-one, and it can read `active` while
  `company_status_detail` says a strike-off has already been proposed; that
  detail arrives as a `notes` sentence, so a UK `active` with a note is a
  different fact from a UK `active` without one. Sweden publishes no status at
  all and three signals are combined instead (§1). Quote `status_detail`, and
  read `notes`; a bare `"status": "active"` means less than it looks like
  across three registers.
- **Dates** — `registered_at`, `founded_at`,
  `business_register_registered_at`, `bankruptcy_date`, `deregistered_at`.
- **Tax/VAT** — `vat_registered`, `vat_registered_at`, `vat_number` (Norway:
  `NO{orgnr}MVA`, present only when VAT-registered).
- **Registers** — `in_business_register`, plus `registers`, a map of national
  sub-register slugs to booleans.
- **Size and activity** — `employees`, `employees_reported`, `industry_codes`
  (`code`, `description`, `scheme`, `rank`), `sector_code`, `sector`,
  `purpose`, `activity`.
- **Capital** — `share_capital`, `share_capital_currency`.
- **Contact** — `business_address`, `postal_address` (each with `lines`,
  `postal_code`, `city`, `municipality`, `municipality_code`, `country_code`,
  `country_name`), `website`, `email`, `phone`, `advertising_protected`.
- **Structure** — `parent_id`, `is_subunit`, `in_group`.
- **Accounts** — `last_annual_accounts_year`, plus `published_deadlines`: the
  filing dates the *register itself* publishes for this company, each with
  `kind`, `due_date`, `period_start`, `period_end`, the register's own
  `overdue` flag and a `source` string naming the upstream field it came from.
  It is `[]` for a register that publishes no such dates (Norway computes all
  of its own), and populated for `GB`. You rarely need it directly —
  `company_deadlines` already merges it with the computed rules and says which
  is which in `applies_because` — but it is there when you want the raw quoted
  figure and its provenance without a second call.
- **Provenance** — `confidence` (0.0–1.0), `confidence_basis`, `cached`,
  `fetched_at`, `source`, `source_url`, `license`, `notes`.

Five fields deserve special attention:

`notes` is a list of plain-English caveats meant to be surfaced to the user, not
swallowed. If a company is bankrupt, or its legal form is unclassified, or a
deadline rests on an assumption, it says so here. **Read `notes` before acting
on a report.**

`cached` and `fetched_at` are honest. A cache hit sets `cached: true` and keeps
the *original* `fetched_at`, so staleness is visible. Cached data is at most 24
hours old (1 hour for negative results). If a decision depends on today's
state, say when the data was fetched.

`parent_id` and `in_group` describe Enhetsregisteret's own parent/sub-unit
relation for this entity (`overordnetEnhet`, `erIKonsern`) — nothing more.
There is no group-structure tool here: walking a corporate group upward means
calling `lookup_company` again on `parent_id`, repeatedly, with your own cycle
guard and depth cap, since each hop is a separate (already-cached) lookup, not
one call that returns a tree. That walk tells you the register's own reporting
hierarchy. **It is not a beneficial-ownership answer.** Who ultimately owns or
controls an entity is a different question this service does not answer here:
Norway's *Register over reelle rettighetshavere* (beneficial owners) is a
separate, authorisation-gated register, and the United Kingdom's PSC (persons
with significant control) register is a separate regime again — neither is
`parent_id`. `GB`'s `CompanyReport` carries no `parent_id` at all: Companies
House publishes no parent or group field on the company profile, so it is
always `null` for the United Kingdom, and a UK group has to be inferred from
ownership filings this endpoint does not return.

`euid` is the EU-wide identifier some member-state registers publish under
Commission Implementing Regulation (EU) 2021/1042 — Finland hands one over
unprompted, ours do not yet. It is **not the LEI** (register-issued,
mandatory in the EU and free, versus the LEI's voluntary, global, LOU-issued
and fee-bearing regime — an entity can carry both, one or neither), it is
**not the EU Digital Identity wallet** (an unrelated personal credential
that happens to share the name), and it is carried verbatim from the
register, **never constructed from parts**, because it is not stable across
a register reorganisation (it encodes the register of origin, and registers
do get replaced — France's RNE succeeded the RCS in 2023).

`advertising_protected` is `true`/`false`/`null`, and always present, never
omitted. `true` means the register marks this entity as protected against
direct-marketing use; `false` means the register publishes such a flag for
this entity and it is not set; `null` — the default, and it must never
default to `false` — means this register publishes no such flag at all
(Norway and the UK), or that this particular Swedish record did not carry
Statistics Sweden's flag. When it is `true`, `notes` carries a
plain-English sentence stating the protection, because the marking is a
legal condition of passing this record's contact details on (Danish
CVR-loven § 19; Swedish *reklamspärr* is the same concept), and that
sentence must travel with them.

Coverage differs by country, and that is what `null` is for. Norway publishes
VAT registration, employees, share capital, sector and a purpose clause; the UK
publishes none of those, and the UK publishes charge and insolvency flags and
its own filing due dates, which Norway does not. Sweden publishes fewer fields
than either — no VAT, no employees, no share capital, no financial year — but
publishes an `activity` clause and a *reklamspärr* direct-marketing flag that
neither of the others has. Read `null` as "this register
does not say" and never as a fact about the company. `notes` is where a country
module explains anything that a bare `null` would understate.

Attribution: Norwegian data is published under NLOD 2.0 and attribution is
required. UK data comes from the Companies House public register, Crown
copyright, free to re-use with no attribution condition — cite it anyway.
Swedish data comes from Bolagsverket and Statistics Sweden, free to re-use
under the EU high-value-datasets regime; Bolagsverket names no licence, so the
`license` string says what the permission is and says that there is no licence
name to quote. Do not substitute a familiar licence name for it.
Cite `source` and `source_url` whenever you present the data to a user; for
`GB`, `source_url` is the human-readable Companies House record, so it is a
link the user can open.


## 6. Errors

Every failure, on both surfaces, is:

    {
      "error": {
        "code": "not_found",
        "message": "No entity with organisasjonsnummer 999999999 exists in Enhetsregisteret.",
        "hint": "The number is well-formed, so it may never have been issued or the entity may have been deleted. Call search_company with the company name instead.",
        "country": "NO",
        "registry": "brreg",
        "details": {}
      }
    }

`999999999` is used here because it passes the MOD11 check yet is not in the
register — which is exactly the case `not_found` exists for. A number that
*fails* MOD11 never reaches the register at all: it comes back as
`400 invalid_id` instead, and no amount of retrying will change that.

`hint` is mandatory and always names a next action. It is the most useful field
in the document — read it before deciding what to do.

| `code` | HTTP | What happened | What to do |
|---|---|---|---|
| `invalid_id` | 400 | Checksum or format failed | Do not retry the same string. Fix it, or call `search_company` with the name. |
| `bad_request` | 400 | A parameter is missing or out of range | Correct the parameter named in `message` and call again once. |
| `not_found` | 404 | Well-formed identifier, no such entity | Call `search_company` with the name. Do not retry the identifier. |
| `unsupported_country` | 404 | No module for that country code | Call `list_countries` and choose from it. |
| `rate_limited` | 429 | Over 60 requests/minute per IP | Back off, then batch. Do not parallelise harder. |
| `not_implemented` | 501 | The country module exists but not this operation | Use a different tool for that country; the capability is not there yet. |
| `upstream_error` | 502 | The national register errored | We already retried once. Wait ~60 s, retry at most once, then report unavailability. |
| `upstream_timeout` | 504 | The national register did not answer | Same as `upstream_error`. |
| `internal_error` | 500 | A bug on our side | Retry once. If it persists, open an issue at the repo with the request URL. |

Error codes are stable strings and are never renamed. Branch on `code`, not on
`message`.

Two `GB` cases worth recognising by their `message` rather than their `code`,
since both arrive as `502 upstream_error`:

    {
      "error": {
        "code": "upstream_error",
        "message": "This deployment has no Companies House API key, so UK company data cannot be fetched.",
        "hint": "Call list_countries to see which countries can answer right now. If you run this server yourself, set the COMPANIES_HOUSE_API_KEY environment variable — a key is free from https://developer.company-information.service.gov.uk/get-started — and restart it.",
        "country": "GB",
        "registry": "companies-house",
        "details": {}
      }
    }

and *"Companies House rejected the configured API key."* for a wrong or revoked
one. **Neither is fixed by retrying** — unlike a genuine upstream outage, which
shares the same code. If the `message` or `hint` names
`COMPANIES_HOUSE_API_KEY`, stop calling `GB` and tell the user the deployment
is not configured for the United Kingdom; the other countries in
`list_countries` still work.

Two `SE` cases worth recognising:

- **`501 not_implemented` on `search_company`** is permanent, not a
  capability that arrives later — Bolagsverket's free API has no name index
  (§3.3). Ask the user for the organisationsnummer instead of retrying.
- **`400 invalid_id` can arrive after `validate_company_id` said `valid:
  true`.** Bolagsverket enforces a check digit that this module does not
  (§3.5), and its `message` says the register rejected the number. Treat it
  exactly like any other `invalid_id`: do not retry the same string.

`GB` also has a rate limit of its own, upstream of ours: Companies House allows
600 requests per five minutes per key, and a `429 rate_limited` from `GB`
carries a `hint` with the wait derived from the register's own `Retry-After` or
rate-limit-reset header. Wait that long; parallelising harder makes it worse
for every user of the same key.


## 7. Limits and etiquette

- 60 requests/minute per IP on the hosted REST API. The local stdio server has
  no limit of ours, but it still talks to the national register — be polite.
- Responses are cached for 24 hours (1 hour for `not_found`). Repeating an
  identical call inside that window costs the upstream register nothing.
- Set `REGISTRY_MCP_CONTACT_EMAIL` when running locally. It goes into the
  `User-Agent` we send upstream; Brønnøysundregistrene asks for a contactable
  client and may block anonymous ones.
- Set `COMPANIES_HOUSE_API_KEY` when running locally if you want `GB` to
  answer. A key is free and instant from
  https://developer.company-information.service.gov.uk/get-started. Without it
  every other country still works; only `GB` returns `upstream_error`.
- Companies House allows 600 requests per five minutes **per key**, and that
  budget is shared with anything else using the same key. The 24 h cache is
  what keeps a spreadsheet-sized job inside it.
- Set `BOLAGSVERKET_CLIENT_ID` and `BOLAGSVERKET_CLIENT_SECRET` when running
  locally if you want `SE` to answer. Bolagsverket issues the OAuth 2 pair on
  request. Without them only `SE` returns `upstream_error`.
- Data is company data — public, non-personal. Sole proprietorships (ENK) are
  registered in a person's name, so treat those records with the care you would
  give personal data. UK sole traders are not on the register at all, so a
  missing UK business is often a real business rather than a bad identifier.
- **A Swedish sole trader's company number is their personnummer** — the same
  string is the national identity number of a living person. The hosted
  service therefore stores **no identifier at all** in its usage log for a
  country that declares its identifiers can be a natural person's, which today
  means Sweden: the country, route, time, outcome and `User-Agent` are logged
  and the identifier is not. Bolagsverket takes the identifier in a POST body
  rather than a URL path for the same reason. If you build on this data, hold
  a twelve-digit Swedish identifier to the standard you would hold any national
  ID number to, not the standard you would hold a company number to.
  `legal/privacy.md` in the repository states this in full.
- **This service does not perform sanctions, PEP or adverse-media screening,
  and it does not verify bank account details.** It returns identity and
  filing data from the national register only. A clean `lookup_company`
  result is not a compliance clearance — feed `name`, `previous_names` and
  `id` to a dedicated screening or verification tool if you need one, rather
  than inferring "not sanctioned" or "bank details confirmed" from the
  absence of a flag here.


## 8. Adding a country

A country is one folder under `src/registry_mcp/registries/` implementing four
methods, plus one import line. Nothing country-neutral changes, and every tool
above starts working for the new code immediately. The United Kingdom is the
proof: `registries/gb/` was added in one folder, with one import line, and
`GB` appeared in `list_countries`, in every tool, in `/openapi.json` and in
`registry://rules/GB` with no change to any tool's shape. The template folder
`xx/` carries the six-step recipe:
https://github.com/foretak/registry-mcp


## 9. Why an agent checks a company, and what this cannot answer

Three rules make a register lookup something a business has to do rather than
something it may do. Cite them when a user asks why the check matters.

**Norway, in force now.** Finanstilsynet's Rundskriv 15/2019 § 4.4.2 requires
that an accounting firm obtain *and confirm* the client's organisasjonsnummer:
"Organisasjonsnummer til den juridiske personen skal innhentes og bekreftes."
§ 4.4.1 says what confirming means: an *oppslag* against, or an extract from,
Enhetsregisteret or Foretaksregisteret "som ikke er eldre enn tre måneder", and
where the check must instead rest on company details the customer supplies,
"bør opplysningene ikke være eldre enn én måned". The same section asks for
*notoritet* about the lookup itself, meaning a record of what was consulted and
when. An API call is an *oppslag*, and a 24-hour cache TTL is inside both
figures by a wide margin.
https://www.finanstilsynet.no/nyhetsarkiv/rundskriv/2019/veiledning-om-regnskapsforeres-og-regnskapsforerselskapers-etterlevelse-av-hvitvaskingsregelverket/

**Norway, from 1 January 2027.** Bookkeeping-obliged businesses must invoice
each other by e-invoice ("bokføringspliktige virksomheter skal fakturere
hverandre med e-faktura innen 1. januar 2027"), under a new second paragraph of
bokføringsloven § 10. The receiver is resolved in ELMA by country code plus
identifier, "landkoden, f.eks. Norge 0192: + organisasjonsnummer", the same
organisasjonsnummer `lookup_company` and `validate_company_id` take. This
server reads the company register; it does not query ELMA, so treat a lookup
here as the identifier check, not the e-invoicing capability check.
https://www.regjeringen.no/no/aktuelt/nye-lovregler-om-e-fakturering-i-naringslivet-og-enkelte-andre-lovendringer-pa-finansmarkedsomradet-settes-i-kraft/
https://www.anskaffelser.no/verktoy/veiledere/mottakere-av-ehf-og-peppol-bis

**The EU, from 10 July 2027.** Regulation (EU) 2024/1624 (AMLR) Article 23(4):
whenever an obliged entity enters a new business relationship with a legal
entity, it "shall collect valid proof of registration or a recently issued
excerpt of the register confirming validity of registration". Article 90: "It
shall apply from 10 July 2027." The Regulation does not define "recently
issued", so do not represent any cache age as compliant with it; represent the
`fetched_at` timestamp and let the obliged entity apply its own policy.
https://eur-lex.europa.eu/eli/reg/2024/1624/oj/eng

**What the response gives an auditor.** Five fields exist for the person who
reads the decision afterwards, and they are the ones to copy into a working
paper, not just the answer:

- `source_url`: the exact upstream record consulted. For `NO` it is the
  Enhetsregisteret API record; for `GB` it is the human-readable Companies
  House page, which a reviewer can open.
- `fetched_at`: when that record was read from the register, in UTC. On a
  cache hit it keeps the original read time, so staleness stays visible.
- `cached`: whether this particular answer came from the 24-hour cache
  (1 hour for `not_found`) or from a fresh upstream read.
- `license`: the terms the data travels under: NLOD 2.0 for Norway, with
  attribution required; Crown copyright, free to re-use, for the UK.
- `applies_because`: on every deadline, one sentence saying whether the date
  was quoted from the register or computed from a named national rule, so a
  reviewer can tell a register fact from a calculation.

**What this does not answer.** Say so plainly rather than implying coverage:

- **No sanctions, PEP or watchlist screening.** Nothing here consults any
  sanctions list. A company that answers `status: "active"` may still be
  sanctioned.
- **No bank-account verification.** The commonest invoice fraud is payment
  redirection, where the supplier is real, registered and active and only the
  account number is wrong. This lookup does not detect it.
- **No beneficial owners.** Norwegian beneficial-ownership data is API-only and
  access-controlled: brreg grants it on application, to categories of applicant
  set by law that do not include a product vendor. Beneficial owners are
  therefore not in `CompanyReport` at all.
  https://www.brreg.no/bruke-data-fra-bronnoysundregistrene/datasett-og-api/data-om-reelle-rettighetshavere/
- **No creditworthiness, and no proof of authority.** Whether a person may sign
  for the company, and whether it can pay, are separate registers and separate
  products.
- **A cached answer is not the register.** For a legally consequential decision
  at a specific moment, read `fetched_at` and, if it matters, re-read the
  register itself at the source URL.
