Matching
Matching a company
POST /v1/match evaluates every measure against what you know about one company and one project, and answers which plausibly fit, why, what is still unknown, and what each could be worth.
Who decides
The request
curl -X POST "https://api.subsido.be/v1/match" \ -H "Authorization: Bearer sb_live_..." \ -H "Content-Type: application/json" \ -d '{"company":{"postcode":"9000","employees":12,"turnover_eur":1500000,"legal_form":"bv","nace":["62.010"]},"project":{"topics":["digitalisation","investment"],"budget_eur":25000,"planned_start":"2026-11-01"},"language":"en","limit":10}'| Property | Meaning |
|---|---|
| company | What you know about the company. Every property is optional. |
| project | What the company wants support for. Every property is optional. |
| options | Which measures to consider. See below. |
| language | nl, fr or en (default): titles, summaries, rule labels and questions. |
| limit | Matches to return, 1 to 100 (default 25). The count of all matches is in matched either way. |
The body is strict: a property the API does not know, anywhere in it, or a value of the wrong type or outside its vocabulary, is 400 invalid_parameter naming it, so a misspelt turnover never silently turns into an unknown. A value that parses but cannot be right (a postcode outside Belgium, an enterprise number whose check digits fail, a negative amount) is 422 invalid_body with the property in details.field. Send Content-Type: application/json.
A fact you do not give is unknown, never assumed. That is the point: instead of a confident score built on guesses, the answer says which facts would settle each measure.
company
| Property | Type | Meaning |
|---|---|---|
| enterprise_number | string | The KBO/BCE number in any usual form (BE0123.456.749, 0123 456 749, or the old nine digits). Validated with its mod-97 check and echoed back as 0123.456.749. An identifier only: no register is consulted, so it adds no facts. |
| postcode | string | The Belgian postcode of the seat or of the operating unit concerned. Decides the region and the province when you do not give them. |
| region, regions | string, string[] | Where the company has an establishment: flanders, brussels, wallonia. region is shorthand for a list of one; both may be given and are combined. |
| province | string | One of the ten provinces (east_flanders, liege, ...). Brussels is in none. |
| size | string | micro, small, medium or large. Derived from the figures below when absent. |
| employees | number | Staff headcount in annual work units (FTE). |
| turnover_eur, balance_sheet_eur | number | The last closed financial year’s annual turnover and balance sheet total. |
| nace | string[] | The company’s NACE-BEL codes, with or without dots: 62.010, 62010, 62.01. Give at least the two-digit division. |
| legal_form | string | The abbreviation people type, in Dutch or French: bv/srl (and the old bvba/sprl), nv/sa, cv/sc, vof/snc, commv/scs, maatschap, eenmanszaak/entreprise individuelle, vzw/asbl, ivzw/aisbl, stichting/fondation, se, public, other. Case, dots and spaces are ignored. |
| applicant_type | string | One of companyself_employednon_profitpublic_bodyresearch_organisationindividualfarmersocial_enterprise Derived from the legal form when absent. |
| founded_on | date | YYYY-MM-DD. Gives the company’s age. |
| age_years | number | The age directly, when you do not have the date. |
| has_legal_personality | boolean | Derived from the legal form when absent: a sole proprietorship and a maatschap have none. |
| de_minimis_received_eur | number | De minimis aid received over the last three years. |
| in_difficulty | boolean | Whether the company is an undertaking in difficulty in the EU state aid sense. |
project
| Property | Type | Meaning |
|---|---|---|
| topics | string[] | What the project is about, as topic ids (digitalisation, energy_efficiency, ...; GET /v1/taxonomy lists them). Used for relevance, and to leave out measures about something else. |
| budget_eur | number | The project’s cost. Used by budget rules and by the estimate. |
| started | boolean | Whether the project has started. Many schemes refuse costs made before the application. |
| planned_start | date | Gives started when you do not: a start on or before today means started. |
| region | string | Where the project or investment takes place, when that is not simply where the company is. |
| cost_types | string[] | The costs to support: training, consultancy, equipment, software, personnel, ... (the eligible cost list). |
| duration_months | number | How long the project lasts. |
| partners | number | Independent partners carrying out the project, the applicant included. |
options
| Property | Default | Meaning |
|---|---|---|
| statuses | open, continuous, forthcoming, unknown | The measure statuses to consider. |
| instrument_types | all | Only these instrument types (grant, loan, tax_deduction, ...). |
| include_not_eligible | false | Also return the measures a rule rules out, as not_eligible, with the rule that failed. |
| include_unrelated | false | With project topics given, a measure whose topics share nothing with them (not even a related topic) is left out, unless this is set. A measure with no topics at all is kept either way. |
What is derived
Facts you did not give are worked out from facts you did, conservatively, and every derivation is reported in profile.derived with the properties it came from. estimate: true marks one that rests on an assumption.
| Derived | From | How |
|---|---|---|
| company.regions | company.postcode | A Belgian postcode determines its region. Only when you gave neither region nor regions. |
| company.province | company.postcode | And its province, except for Brussels, which has none. Only when you gave no province. |
| company.size | employees, turnover_eur, balance_sheet_eur | The EU SME definition, below. With a headcount alone, the headcount decides and estimate is true. |
| company.applicant_type | company.legal_form | bv, nv, cv, vof, commv, maatschap and se are a company; eenmanszaak is self-employed; vzw, ivzw and stichting are a non-profit; public is a public body. The French abbreviations map the same way. |
| company.has_legal_personality | company.legal_form | False for eenmanszaak and maatschap, true for the other forms, unknown for other. |
| company.age_years | company.founded_on | Completed years on today’s Brussels date: founded 1 October 2019 is six years old on 27 September 2026, not seven. A date in the future is ignored with a warning. |
| project.started | project.planned_start | Started when the planned start is today or earlier. |
| project.region | the company’s regions | When the company is in exactly one region and you gave no project region. estimate is true: the project is assumed to be where the company is. |
Size bands
Recommendation 2003/361/EC: a headcount ceiling and either the turnover or the balance-sheet ceiling. A company over a band’s financial ceilings moves up a band.
| Band | Staff (AWU) | Turnover | or balance sheet |
|---|---|---|---|
| micro | fewer than 10 | at most €2m | at most €2m |
| small | fewer than 50 | at most €10m | at most €10m |
| medium | fewer than 250 | at most €50m | at most €43m |
| large | everything else |
Partner and linked enterprises change the figures the definition uses, and the API cannot see them. That is one reason a match is “likely”, not “eligible”. Some measures (the Belgian tax shelter, for example) use the Belgian company code’s own size test instead; their rules then test the figures directly, so send the figures rather than only size.
The four statuses
Each measure’s rules are evaluated with three-valued logic (pass, fail, unknown; see eligibility rules), and the outcome, together with how good the measure’s rules are, gives the status:
| match_status | Exactly when |
|---|---|
| likely_eligible | Every rule passed, and the measure’s rules are curated or extracted (rules_basis) with a confidence of at least 0.7. As strong as this API gets. |
| possibly_eligible | No rule failed, and at least one could not be decided because a fact is missing. unknowns lists the facts that would decide it. |
| needs_review | Every rule passed, but the rules are only derived from structured fields, or their confidence is below 0.7: the conditions that decide are mostly prose that is not evaluated. Read the official page. A measure with no rules at all is here too. |
| not_eligible | A rule failed; reasons says which. Only returned with options.include_not_eligible. |
Matches are sorted by status in that order, then by what can be applied for soonest (open, continuous, forthcoming, unknown, paused, budget exhausted, closed), then by score, then by the nearest deadline.
The response
{
"matches": [
{
"subsidy_id": "federal:investeringsaftrek",
"match_status": "likely_eligible",
"confidence": 0.9,
"relevance": 1,
"score": 0.98,
"reasons": [
{
"id": "applicant_type",
"field": "company.applicant_type",
"op": "in",
"value": [
"company",
"self_employed",
"farmer"
],
"label": "Industrial, commercial or agricultural business, or liberal profession",
"result": "pass",
"actual": "company",
"provenance": {
"method": "curated",
"source_url": "https://financien.belgium.be/nl/ondernemingen/vennootschapsbelasting/belastingvoordelen/investeringsaftrek",
"source_section": "Wie kan de investeringsaftrek genieten?",
"review_status": "verified",
"reviewed_at": "2026-09-27",
"note": null
}
}
],
"unknowns": [],
"matched_topics": [
"digitalisation",
"investment"
],
"estimate": {
"max_amount_eur": 10000,
"rate_applied": 40,
"basis": "€25,000 × 40%"
},
"subsidy": {
"id": "federal:investeringsaftrek",
"title": "Investment deduction",
"summary": "Tax deduction from FOD Financiën for companies, self-employed people and farmers in Belgium. Covers 10% to 40% of eligible costs. Applications accepted at any time.",
"status": "continuous",
"instrument_type": "tax_deduction",
"issuer": "FOD Financiën",
"scope": "national",
"regions": [],
"topics": [
"investment",
"digitalisation",
"energy_efficiency",
"renewable_energy"
],
"opens_at": null,
"closes_at": null,
"rolling": true,
"budget_exhaustion_possible": false,
"funding": {
"rate_min": 10,
"rate_max": 40,
"amount_min": null,
"amount_max": null,
"annual_cap": null,
"de_minimis": null
},
"rules_basis": "curated",
"source": {
"id": "curated_federal",
"url": "https://financien.belgium.be/nl/ondernemingen/vennootschapsbelasting/belastingvoordelen/investeringsaftrek",
"authority": "FOD Financiën",
"last_checked_at": "2026-09-27T06:00:12+02:00",
"freshness": "fresh"
},
"links": {
"api": "https://api.subsido.be/v1/subsidies/federal:investeringsaftrek",
"web": "https://subsido.be/en/grants/investeringsaftrek",
"apply": "https://financien.belgium.be/nl/ondernemingen/vennootschapsbelasting/belastingvoordelen/investeringsaftrek"
}
}
},
{
"subsidy_id": "federal:tax-shelter-start-up",
"match_status": "possibly_eligible",
"confidence": 0.9,
"relevance": 0.25,
"score": 0.515,
"reasons": [
{
"id": "size",
"field": "company.employees",
"op": "lte",
"value": 50,
"label": "At most 50 employees (annual average)",
"result": "pass",
"actual": 12,
"provenance": {
"method": "curated",
"source_url": "https://financien.belgium.be/nl/ondernemingen/tax-shelter-kleine-ondernemingen/startende-start-up",
"source_section": "Art. 1:24 WVV",
"review_status": "verified",
"reviewed_at": "2026-09-27",
"note": null
}
},
{
"id": "size_turnover_a",
"field": "company.turnover_eur",
"op": "lte",
"value": 11250000,
"label": "Annual turnover at most EUR 11,250,000 (excl. VAT)",
"result": "pass",
"actual": 1500000,
"provenance": {
"method": "curated",
"source_url": "https://financien.belgium.be/nl/ondernemingen/tax-shelter-kleine-ondernemingen/startende-start-up",
"source_section": "Art. 1:24 WVV",
"review_status": "verified",
"reviewed_at": "2026-09-27",
"note": null
}
},
{
"id": "size_employees_b",
"field": "company.employees",
"op": "lte",
"value": 50,
"label": "At most 50 employees (annual average)",
"result": "pass",
"actual": 12,
"provenance": {
"method": "curated",
"source_url": "https://financien.belgium.be/nl/ondernemingen/tax-shelter-kleine-ondernemingen/startende-start-up",
"source_section": "Art. 1:24 WVV",
"review_status": "verified",
"reviewed_at": "2026-09-27",
"note": null
}
},
{
"id": "size_balance_b",
"field": "company.balance_sheet_eur",
"op": "lte",
"value": 6000000,
"label": "Balance sheet total at most EUR 6,000,000",
"result": "unknown",
"actual": null,
"provenance": {
"method": "curated",
"source_url": "https://financien.belgium.be/nl/ondernemingen/tax-shelter-kleine-ondernemingen/startende-start-up",
"source_section": "Art. 1:24 WVV",
"review_status": "verified",
"reviewed_at": "2026-09-27",
"note": null
}
},
{
"id": "size_turnover_c",
"field": "company.turnover_eur",
"op": "lte",
"value": 11250000,
"label": "Annual turnover at most EUR 11,250,000 (excl. VAT)",
"result": "pass",
"actual": 1500000,
"provenance": {
"method": "curated",
"source_url": "https://financien.belgium.be/nl/ondernemingen/tax-shelter-kleine-ondernemingen/startende-start-up",
"source_section": "Art. 1:24 WVV",
"review_status": "verified",
"reviewed_at": "2026-09-27",
"note": null
}
},
{
"id": "size_balance_c",
"field": "company.balance_sheet_eur",
"op": "lte",
"value": 6000000,
"label": "Balance sheet total at most EUR 6,000,000",
"result": "unknown",
"actual": null,
"provenance": {
"method": "curated",
"source_url": "https://financien.belgium.be/nl/ondernemingen/tax-shelter-kleine-ondernemingen/startende-start-up",
"source_section": "Art. 1:24 WVV",
"review_status": "verified",
"reviewed_at": "2026-09-27",
"note": null
}
},
{
"id": "age_max",
"field": "company.age_years",
"op": "lt",
"value": 4,
"label": "Within the first four years after incorporation",
"result": "unknown",
"actual": null,
"provenance": {
"method": "curated",
"source_url": "https://financien.belgium.be/nl/ondernemingen/tax-shelter-kleine-ondernemingen/startende-start-up",
"source_section": null,
"review_status": "verified",
"reviewed_at": "2026-09-27",
"note": "Completed years: the fifth year after incorporation belongs to the scale-up regime."
}
}
],
"unknowns": [
{
"field": "company.age_years",
"request_path": "company.founded_on",
"question": "When was the company founded?"
}
],
"matched_topics": [],
"estimate": {
"max_amount_eur": 11250,
"rate_applied": 45,
"basis": "€25,000 × 45%, capped at €500,000"
},
"subsidy": {
"id": "federal:tax-shelter-start-up",
"title": "Tax shelter for start-ups",
"summary": "Tax credit from FOD Financiën for small companies in Belgium. Covers 30% to 45% of eligible costs, up to €500,000. Applications accepted at any time.",
"status": "continuous",
"instrument_type": "tax_credit",
"issuer": "FOD Financiën",
"scope": "national",
"regions": [],
"topics": [
"starting_business",
"financing"
],
"opens_at": null,
"closes_at": null,
"rolling": true,
"budget_exhaustion_possible": false,
"funding": {
"rate_min": 30,
"rate_max": 45,
"amount_min": null,
"amount_max": 500000,
"annual_cap": null,
"de_minimis": null
},
"rules_basis": "curated",
"source": {
"id": "curated_federal",
"url": "https://financien.belgium.be/nl/ondernemingen/tax-shelter-kleine-ondernemingen/startende-start-up",
"authority": "FOD Financiën",
"last_checked_at": "2026-09-27T06:00:12+02:00",
"freshness": "fresh"
},
"links": {
"api": "https://api.subsido.be/v1/subsidies/federal:tax-shelter-start-up",
"web": "https://subsido.be/en/grants/tax-shelter-start-up",
"apply": "https://financien.belgium.be/nl/ondernemingen/tax-shelter-kleine-ondernemingen/startende-start-up"
}
}
}
],
"matched": 2,
"evaluated": 412,
"profile": {
"facts": {
"company.applicant_type": "company",
"company.employees": 12,
"company.has_legal_personality": true,
"company.legal_form": "bv",
"company.nace": [
"62010"
],
"company.province": "east_flanders",
"company.regions": [
"flanders"
],
"company.size": "small",
"company.turnover_eur": 1500000,
"project.budget_eur": 25000,
"project.region": "flanders",
"project.started": false,
"project.topics": [
"digitalisation",
"investment"
]
},
"derived": [
{
"field": "company.regions",
"value": [
"flanders"
],
"from": [
"company.postcode"
],
"estimate": false
},
{
"field": "company.province",
"value": "east_flanders",
"from": [
"company.postcode"
],
"estimate": false
},
{
"field": "company.size",
"value": "small",
"from": [
"company.employees",
"company.turnover_eur"
],
"estimate": false
},
{
"field": "company.applicant_type",
"value": "company",
"from": [
"company.legal_form"
],
"estimate": false
},
{
"field": "company.has_legal_personality",
"value": true,
"from": [
"company.legal_form"
],
"estimate": false
},
{
"field": "project.started",
"value": false,
"from": [
"project.planned_start"
],
"estimate": false
},
{
"field": "project.region",
"value": "flanders",
"from": [
"company.region"
],
"estimate": true
}
],
"enterprise_number": null,
"warnings": []
},
"missing_information": [
{
"field": "company.age_years",
"request_path": "company.founded_on",
"question": "When was the company founded?",
"would_settle": 1
}
],
"language": "en",
"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.",
"meta": {
"request_id": "req_0192d3a4b5c67d8e9f0a1b2c3d4e5f61",
"warnings": []
}
}Illustrative values. The Tax Shelter match shows the logic at work: its size test is “two of three criteria”, written as an any of three pairs. The first pair passed, so the unknown balance sheet in the other two does not matter and is not asked for; the company’s age is unknown and would decide, so it is.
| Field | Meaning |
|---|---|
| matches[] | Best first, at most limit. |
| matches[].subsidy_id, match_status | The measure and the status above. |
| matches[].confidence | 0 to 1: how far the rules capture what decides eligibility for this measure (its eligibility.confidence). |
| matches[].relevance | 0 to 1: how well the measure’s topics fit the project’s. A shared topic counts fully, a related one (energy and energy efficiency, investment and financing) half. 0.5 when you gave no topics; 0.25 for a measure with no topics. |
| matches[].score | 0.5 × relevance + 0.3 × status weight (1, 0.7, 0.5, 0) + 0.2 × confidence, to three decimals. For ordering, not a probability. |
| matches[].reasons[] | Every condition in the measure’s rule tree, in order: id, field, op, value, a label in your language, the result (pass, fail or unknown), the company’s actual fact (or null), and the rule’s provenance: where it was read, and how. |
| matches[].unknowns[] | The facts that were unknown where knowing them could change the outcome: field, the request_path that supplies it, and the question to ask, in your language. Empty once the outcome is decided. |
| matches[].matched_topics | The project topics the measure shares. |
| matches[].estimate | See below, or null. |
| matches[].subsidy | What you need to show the measure without a second request: title, summary, status, instrument type, issuer name, scope, regions, topics, dates, funding, rules_basis, the source with its official URL and when it was last checked, and links (api, web, apply). |
| matched | How many measures matched in all, before limit. |
| evaluated | How many measures were evaluated. |
| profile | The facts evaluated (facts, keyed by rule field), what was derived and from what, the normalised enterprise_number, and warnings. |
| missing_information[] | Across the returned matches, the missing facts that would settle the most of them, most first: field, request_path, question and would_settle (how many matches it appears in). Ask these, send the answers, and possibly_eligible turns into a decided status. |
| language, disclaimer, meta | The language used, the independence sentence, and the request id. |
Unknown into pass
request_path is the property to send, which is not always the rule’s field: an age rule on company.age_years asks for company.founded_on, and a region rule on company.regions asks for company.region. Build the follow-up form from missing_information, send the same request with the new facts, and the unknowns become passes or failures.The estimate
Arithmetic on the published figures, indicative only:
- The rate is the highest tier in
funding.ratesfor the company’s size, orfunding.rate_maxwhen no tier names that size. - The cap is the lower of
funding.amount_maxandfunding.annual_cap. - With a project budget and a rate: budget × rate, capped. Otherwise, when there is a cap: the cap, as the maximum amount. Otherwise
null. basissays the sum in words, in your language (€25,000 × 40%,capped at €7,500). For a tax measure the figure is the size of the advantage the rate describes, not a payment.
What a match costs
Each POST /v1/match is one request and one match evaluation, whatever the number of measures evaluated. A request refused for invalid facts is not counted as an evaluation. Match evaluations have their own monthly allowance, so you can keep reading measures after it is spent; see rate limits and quotas.
Bulk matching
Business and abovePOST /v1/match/bulk needs the bulk_match capability.
Many companies in one request: up to 500 on Business and 5,000 on Enterprise. Each has your own reference (1 to 200 characters, unique in the request), echoed back.
{
"companies": [
{
"reference": "client-0042",
"company": {
"postcode": "2000",
"employees": 4,
"legal_form": "bv"
},
"project": {
"topics": [
"energy_efficiency"
]
}
},
{
"reference": "client-0043",
"company": {
"postcode": "4000",
"nace": [
"47.110"
]
}
}
],
"options": {
"statuses": [
"open",
"continuous"
]
},
"language": "fr",
"limit_per_company": 10
}The answer is { results, evaluated, language, disclaimer, meta }, one result per company in request order: { reference, matches, matched, missing_information, error }. A company whose facts are invalid gets its own error ({ code, message, details }) and empty matches, and is not counted; the others are one evaluation each. limit_per_company is 1 to 100 (default 10). The whole request is refused with match_quota_exceeded when its valid companies would take you past your allowance, and with plan_required when it has more companies than your plan allows in one request.
To match the same companies again whenever a measure changes, put them on a watchlist instead: re-evaluation is included.
