Documentation

Three tools, one asynchronous pattern, a stable set of error codes.

Connecting

Endpoint https://test.nbb-mcp.be/mcp
Transport MCP streamable HTTP (one endpoint, POST + SSE). Not the deprecated two-endpoint SSE transport.
Authentication Authorization: Bearer nbb_live_… on every request
Session Stateful: the server expects the normal initialize handshake.

Client-by-client instructions are on the setup pages. A request without a key gets 401 with a WWW-Authenticate header; a key over the rate limit gets 429 with Retry-After.

Tools

search_company billable

Resolve a company name to its Belgian enterprise (KBO/BCE) number, or list the names registered for a number.

Parameter Type Required Description
query string yes A name, part of a name, or an enterprise number in any notation (0403.170.701, BE0403170701, 403170701). Typo-tolerant and language-agnostic; numbers bypass the fuzzy search.
language string no nl, fr, de or en. Only orders the returned names; filters nothing out.
limit integer no How many companies to return. Default 10, maximum 25.

Returns. Candidates best match first: kbo_number, every registered name with its type and language, and match_score (1.0 for a direct number hit). Plus warnings[], which explains an empty result or a failed mod-97 check digit.

get_annual_accounts asynchronous billable

Fetch the annual accounts a company filed with the NBB: balance sheet, income statement, appropriation of results, social balance sheet and notes, each line with its official rubric code and a translated label.

Parameter Type Required Description
kbo_number string yes Belgian enterprise number in any notation. Not the name — call search_company first.
year integer no The calendar year the accounting exercise ends in. Omit for the most recent filing.
language string no Label language: nl, fr, de or en. Default nl. Figures are identical in every language.
raw boolean no True returns the filing exactly as the NBB delivers it (XBRL or JSON-XBRL, unparsed). Default false.

Returns. Usually {operation_id, status: "pending", poll_after_seconds} — then poll get_operation. A filing that is already cached comes back immediately with status "completed" and the accounts in result.

get_operation not billable

Check whether an asynchronous request has finished and collect its result.

Parameter Type Required Description
operation_id string yes The id returned by get_annual_accounts. You can only poll operations created with your own API key.

Returns. status pending/processing with poll_after_seconds, completed with result, or failed with error {code, message}.

The asynchronous pattern

Fetching a filing means calling the National Bank, downloading a deposit and parsing XBRL — too slow to hold a tool call open, and far too slow when the filing is a PDF that needs an LLM. So get_annual_accounts hands back an operation and you poll it.

  1. Call get_annual_accounts. Normally you get {"operation_id": "…", "status": "pending", "poll_after_seconds": 3}.
  2. Wait poll_after_seconds, then call get_operation with that id. Repeat while the status is pending or processing.
  3. Stop at completed (the accounts are in result) or failed (there is an error object with a code from the table below).

Two shortcuts are built in. A filing that is already cached comes back from get_annual_accounts immediately with status: "completed" and cached: true — no polling. A cached filing whose JSON is too large to inline (over 100 KB, which happens with raw: true) comes back as an operation with cached: true, and the first poll answers instantly.

Do not re-request while an operation is running. Identical requests are de-duplicated server-side: asking again returns the same operation id and spends another billable call for nothing.

Operations belong to the API key that created them. Polling somebody else's id answers exactly like polling a nonexistent one. Finished operations are cleaned up 30 days after they finish.

Error codes

A failed operation carries error.code, and these strings are stable — a client may branch on them.

Code Meaning What to do
no_accounts_found The NBB has no accepted filing for this company, or none for the year you asked for. The message lists the years that do exist. Retry with one of the years from the message, or tell the user there is nothing to show. Retrying the same request will not help.
pdf_fallback_required The filing exists but only as a PDF (older or non-standardised deposits), or in a taxonomy version this server cannot map yet. Reading a PDF filing needs the paid plan's LLM extraction. Upgrade at /pricing; the raw PDF availability is reported either way.
nbb_unavailable The National Bank's service was unreachable or answered 429/5xx after our own retries. Transient. Retrying in a few minutes is worth it — this is the one code where that is true.
nbb_error The NBB answered, but with something unusable (a 4xx that is not a missing format). Do not retry. Report the message; if it repeats for a company that should have accounts, it is a bug on our side.
deposit_unreadable The deposit downloaded but could not be parsed as XBRL. Already retried automatically by the worker. If it reaches you, the filing itself is the problem.
nbb_not_configured This instance has no NBB subscription key configured. Nothing a client can do; it means the deployment is incomplete.
result_missing The operation completed but its stored result is gone (retention deleted it). Call get_annual_accounts again — the answer will be rebuilt or served from the deposit cache.

Errors that are not operation failures

Bad input, an exhausted quota and an unknown operation id come back as a tool error (MCP isError: true) with an explanatory message rather than as an operation you can poll — there is nothing running to poll.

Situation Response
Monthly quota reached Tool error naming the limit and where to upgrade (/pricing). The refused call itself is recorded but not charged.
PDF-only filing on the free plan Operation fails with pdf_fallback_required: reading a PDF filing needs the paid plan's LLM extraction.
Monthly LLM cap reached (paid plan) Tool error saying the cap is reached. Previously extracted filings keep being served — cached extractions never count.
Not a valid enterprise number Tool error naming the problem and suggesting search_company.
Missing or invalid API key HTTP 401 before MCP sees the request.
Too many requests HTTP 429 with Retry-After, at 30 requests per minute (burst 10) per key.

Limits

Limit Free Paid
Billable tool calls per calendar month 10 2 000
Fresh PDF-LLM extractions per month none — the branch is disabled 50
Requests per minute per key 30 30
Burst 10 10
get_operation polls free, rate limit only free, rate limit only

Quotas reset on the 1st of each month.

The result JSON

Every completed get_annual_accounts result has the same shape:

Field Contents
meta Identifiers, filing and response language, model type, taxonomy version, data_source, confidence, caveats, and the current and previous period dates.
identification Name, address and legal form as filed.
statements[] Stable ids balance_sheet_assets, balance_sheet_liabilities, income_statement, appropriation, social_balance; rows are {code, label, level, current, previous}.
notes[] The note sections of the filing, same row shape.
unmapped_facts[] Facts we could not tie to a rubric code. Never empty out of laziness and never silently dropped — if something is missing from the statements, it is here.

Languages

language accepts nl, fr, de and en, and defaults to nl. Labels come from the National Bank's own taxonomy label linkbases, not from a translation engine, so they match the wording on the official forms.

If a label is missing in the language you asked for, the response falls back in order: requested language → the language the filing was submitted in → nl → fr → en. The figures never change with the language, and meta.response_language tells you what you actually got.

The website itself is published in the same four languages; the switcher is in the navigation and your choice is remembered in a cookie.

Data sources and caveats

meta.data_source says how a result was produced, and it is the field to read before trusting a number:

data_source What it means Confidence
xbrl Parsed from the XBRL instance the company filed. Figures and rubric codes are exactly what was deposited. Authoritative
jsonxbrl Parsed from the JSON-XBRL representation. Same data, different serialisation; results carry a caveat because this payload shape has had less exposure than the XBRL one. Authoritative, with a caveat
pdf_llm The filing exists only as a PDF and was read by an LLM into the same JSON shape. Rubric codes and values are extracted; nothing is translated by the model. Heuristic — meta.confidence carries a balance-check score and meta.caveats says the numbers were read from a document.

Known caveats