Skip to content
Subsido

Using the API

MCP server

A Model Context Protocol server, so an AI agent can search measures, match a company and explain a match with the same key, the same limits and the same answers as the REST API.

The endpoint

PropertyValue
URLPOST https://api.subsido.be/mcp
TransportStreamable HTTP, stateless: every request is one JSON-RPC 2.0 message (or a batch) answered with plain JSON. No session, no server-sent events; a GET is answered 405 method_not_allowed.
AuthenticationYour API key as a bearer token: Authorization: Bearer sb_live_....
Protocol versions2025-06-18, 2025-03-26, 2024-11-05. The server answers initialize with the version you asked for when it supports it, else the newest.
Methodsinitialize, ping, tools/list, tools/call. A notification (a message without an id) is acknowledged with 202.
LimitsThe same as the REST routes: each POST is one request against your rate limit and monthly quota, and the matching tools also count one match evaluation each. The change-feed tool needs the Developer plan, like /v1/changes.

Tools

Every tool is read-only. Arguments are validated exactly as the matching route validates its parameters.

ToolArgumentsReturns
search_subsidiesq, region, topic, company_size, instrument_type, status, nace, deadline_before, language, limit (1 to 25)Compact records, as GET /v1/subsidies: { results, has_more, disclaimer }. First page only.
get_subsidyid (id or slug, required), languageThe full record, as GET /v1/subsidies/{id}.
match_companycompany, project, language, limit (1 to 25, default 10){ matches, matched, missing_information, disclaimer }, as POST /v1/match with default options. One match evaluation.
explain_eligibilitysubsidy_id (required), company, project, languageOne measure evaluated for the company whatever its status or topics, even when a rule rules it out: { match, subsidy, disclaimer }. One match evaluation.
list_changessince, type, source, region, subsidy, cursor, limit (1 to 50){ changes, has_more, next_cursor, latest_seq }: the events of GET /v1/changes, with the same filters, the same cursor and each event in the same shape. Pass next_cursor back as cursor to continue, and again later to get only what is new. Developer plan and above.
list_sourcesnone{ sources }, each with id, name, rights_mode, licence, freshness, measures, enabled.

The company argument advertises the common properties (postcode, region, size, employees, turnover, balance sheet, NACE codes, legal form, founding date, de minimis aid), and accepts every property of a match request. A result comes back both as text (pretty JSON) and as structuredContent. A tool that fails (an invalid argument, a plan the key does not have, a spent allowance) returns a result with isError: true and the API error in structuredContent.error, so the model can read what went wrong; only a malformed message is a JSON-RPC error.

What the server tells the model

The initialize answer carries instructions that end with the independence sentence: “Independent service. Not affiliated with, endorsed by, or operated by any government or public authority. Eligibility and awards are decided by the competent authority.” Every tool result carries it too. A model repeating a match should say “likely” or “possibly” eligible, and link the official page.

Connecting a client

Claude Code

claude mcp add --transport http subsido https://api.subsido.be/mcp \  --header "Authorization: Bearer sb_live_..."

Or in a project’s .mcp.json, reading the key from the environment so it is not committed:

.mcp.json
{
  "mcpServers": {
    "subsido": {
      "type": "http",
      "url": "https://api.subsido.be/mcp",
      "headers": {
        "Authorization": "Bearer ${SUBSIDY_API_KEY}"
      }
    }
  }
}

Claude Desktop

Claude Desktop starts local servers from its configuration file, so a remote server with a key goes through the mcp-remote bridge (it needs Node.js). In claude_desktop_config.json (Settings, Developer, Edit config):

claude_desktop_config.json
{
  "mcpServers": {
    "subsido": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://api.subsido.be/mcp",
        "--header",
        "Authorization:${AUTH_HEADER}"
      ],
      "env": {
        "AUTH_HEADER": "Bearer sb_live_..."
      }
    }
  }
}

The header is passed through an environment variable because some systems split arguments on the space in Bearer …. Restart Claude Desktop after editing.

Other clients

Any client that speaks Streamable HTTP and can send a header works: give it the URL and the Authorization header. Many (Cursor, VS Code, Windsurf) take a JSON block of this shape:

JSON
{
  "mcpServers": {
    "subsido": {
      "url": "https://api.subsido.be/mcp",
      "headers": {
        "Authorization": "Bearer sb_live_..."
      }
    }
  }
}

A client that only runs local servers can use the mcp-remote bridge as above.

By hand

Because the server is stateless, curl is a complete client. List the tools, then call one:

curl -X POST "https://api.subsido.be/mcp" \  -H "Authorization: Bearer sb_live_..." \  -H "Content-Type: application/json" \  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'
curl -X POST "https://api.subsido.be/mcp" \  -H "Authorization: Bearer sb_live_..." \  -H "Content-Type: application/json" \  -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "match_company", "arguments": {"company": {"postcode": "4000", "employees": 8, "legal_form": "srl"}, "project": {"topics": ["digitalisation"], "budget_eur": 15000}, "language": "fr", "limit": 5}}}'
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"matches\": [ … ],\n  \"matched\": 9,\n  …\n}"
      }
    ],
    "structuredContent": {
      "matches": [
        "…"
      ],
      "matched": 9,
      "missing_information": [
        "…"
      ],
      "disclaimer": "Independent service. Not affiliated with, endorsed by, or operated by any government or public authority. Eligibility and awards are decided by the competent authority."
    },
    "isError": false
  }
}
  • Errors of the protocol itself: -32700 for a body that is not JSON (with HTTP 400), -32600 when jsonrpc is not "2.0", -32601 for an unknown method.
  • Without a valid key the endpoint answers 401, and with the key of an account without a plan 402 subscription_required, in the API’s own error shape, before any JSON-RPC is read. See authentication.