Matching
Eligibility rules
A small, closed language for the conditions of a measure, evaluated with three values: pass, fail and unknown.
A measure’s eligibility.rules is a list of rule nodes that must all hold. The matcher evaluates them against the facts of a match request; the full record serves them as they are, so you can show or evaluate them yourself. Topics are kept apart on purpose: a topic is a reason to show a measure, a rule is a condition to meet.
Nodes
| Node | Shape | Result |
|---|---|---|
| condition | { id, field, op, value, label?, provenance } | Compares one fact with a value: pass, fail, or unknown when the fact is missing. |
| all | { "all": [nodes] } | Fail if any child fails; otherwise unknown if any is unknown; otherwise pass. |
| any | { "any": [nodes] } | Pass if any child passes; otherwise unknown if any is unknown (or the list is empty); otherwise fail. |
| not | { "not": node } | Pass and fail swap; unknown stays unknown. |
Nodes are told apart by their keys. The top-level list is an implicit all. A condition’s id is stable within its measure (region, size, applicant_type, nace, age_max), which is how the change feed can say which rule changed: eligibility.rules[age_max].
Here are the rules of the federal Tax Shelter for start-ups: the Belgian company code’s small-company test (at least two of three criteria) as an any of three pairs, then an age limit. Provenance is shortened in the first six.
[
{
"any": [
{
"all": [
{
"id": "size",
"field": "company.employees",
"op": "lte",
"value": 50,
"provenance": "…"
},
{
"id": "size_turnover_a",
"field": "company.turnover_eur",
"op": "lte",
"value": 11250000,
"provenance": "…"
}
]
},
{
"all": [
{
"id": "size_employees_b",
"field": "company.employees",
"op": "lte",
"value": 50,
"provenance": "…"
},
{
"id": "size_balance_b",
"field": "company.balance_sheet_eur",
"op": "lte",
"value": 6000000,
"provenance": "…"
}
]
},
{
"all": [
{
"id": "size_turnover_c",
"field": "company.turnover_eur",
"op": "lte",
"value": 11250000,
"provenance": "…"
},
{
"id": "size_balance_c",
"field": "company.balance_sheet_eur",
"op": "lte",
"value": 6000000,
"provenance": "…"
}
]
}
]
},
{
"id": "age_max",
"field": "company.age_years",
"op": "lt",
"value": 4,
"label": {
"nl": "In de eerste vier jaar na de oprichting",
"fr": "Dans les quatre premières années après la constitution",
"en": "Within the first four years after incorporation",
"de": 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."
}
}
]Three values
A fact the caller did not give is unknown, never assumed, and so is a fact of a shape the operator cannot compare, or a rule whose value is malformed: a broken rule never passes by accident. The outcome of the whole tree is one of the three, and it decides the match status. The facts that were unknown where knowing them could change the outcome are reported as unknowns; once a tree is decided (a failure in an all, a pass in an any) its unknowns are dropped, because nothing is left to ask.
Fields
A closed list: a rule over a fact the matcher cannot be given would be a rule that is always unknown. request_path is the match request property that supplies the fact, and what unknowns and missing_information point you to.
| field | Type | request_path | Meaning |
|---|---|---|---|
| company.regions | list of regions | company.region | Regions where the company has an establishment (its seat or an operating unit). |
| company.province | province | company.province | The province it is established in. |
| company.size | size band | company.size | micro, small, medium or large. |
| company.employees | number | company.employees | Staff (FTE). |
| company.turnover_eur | number | company.turnover_eur | Annual turnover. |
| company.balance_sheet_eur | number | company.balance_sheet_eur | Balance sheet total. |
| company.nace | list of codes | company.nace | NACE-BEL codes, dots removed. |
| company.legal_form | legal form | company.legal_form | bv, nv, cv, vzw, eenmanszaak, ... |
| company.applicant_type | applicant type | company.applicant_type | company, self_employed, non_profit, ... |
| company.age_years | number | company.founded_on | Completed years since it was founded. |
| company.has_legal_personality | boolean | company.has_legal_personality | Whether it is a legal person. |
| company.de_minimis_received_eur | number | company.de_minimis_received_eur | De minimis aid over the last three years. |
| company.in_difficulty | boolean | company.in_difficulty | An undertaking in difficulty. |
| project.topics | list of topics | project.topics | What the project is about. |
| project.budget_eur | number | project.budget_eur | The project’s cost. |
| project.started | boolean | project.started | Whether it has already started (the incentive effect). |
| project.region | region | project.region | Where the project takes place. |
| project.cost_types | list of cost types | project.cost_types | The costs to be supported. |
| project.duration_months | number | project.duration_months | How long it lasts. |
| project.partners | number | project.partners | Independent partners, the applicant included. |
GET /v1/taxonomy returns the same list as rule_fields, each with the question to ask in Dutch, French and English.
Operators
Strings compare case-insensitively. Numbers may be given as JSON numbers or numeric strings.
| op | value | Passes when |
|---|---|---|
| eq, in | string or list | The fact is one of the values. For a list fact (several NACE codes), every element must be one of the values. Also compares two booleans, or two single numbers, for equality. |
| ne, not_in | string or list | No element of the fact is one of the values. |
| contains_any | list | A list fact shares at least one element with the value. |
| contains_all | list | Every element of the value is in the fact. |
| contains_none | list | The fact shares no element with the value. |
| gte, gt, lte, lt | number | The numeric comparison holds. |
| between | [min, max] | min ≤ fact ≤ max. Anything other than exactly two bounds is unknown. |
| is_true, is_false | ignored | The boolean fact is true, or false. |
| nace_any | list of codes | At least one of the company’s codes falls under one of the value’s: a digit prefix (62 covers 62010) or a NACE section letter (J covers 62010). |
| nace_none | list of codes | None of the company’s codes falls under any of the value’s. An empty list of company codes is unknown for both NACE operators. |
Labels
A rule can bring its own label ({ nl, fr, en, de }), usually the words of the official page; the field is left out of the record when it has none. Otherwise a label is generated from the field, operator and value in each service language: Project cost at least €25,000, Projectkost minstens € 25.000, Coût du projet au moins 25 000 €. A match’s reasons carry the label in the language you asked for.
Provenance and review status
Every condition carries where it came from:
| Field | Meaning |
|---|---|
| method | structuredhtml_sectiontext_patternsource_categorykeywordscuratedderived |
| source_url | The page the rule was read from. |
| source_section | The heading of the section it was read from (“Wie komt in aanmerking?”, “Art. 1:24 WVV”). |
| review_status | auto: produced from a structured field. unreviewed: found in the text by a pattern. verified: checked against the official page, on reviewed_at. |
| reviewed_at | YYYY-MM-DD, for verified rules. |
| note | What the rule cannot see, or why it is written as it is. |
Where rules come from
- From structured fields: a regional measure gets a
regionrule (contains_anyoncompany.regions), a size restriction asizerule, a list of applicant types anapplicant_typerule, sector listsnaceandnace_excludedrules. Theirreview_statusisauto. - From patterns in the text (a maximum or minimum company age, for instance):
methodistext_patternandreview_statusisunreviewed. - Written from the official page:
methodiscuratedandreview_statusisverified. Such a rule replaces the automatic rule with the same id.
The measure’s rules_basis sums this up (none, derived, extracted, curated), and its confidence says how much of what decides eligibility the rules capture: 0.9 for curated rules unless set otherwise, 0.6 for extracted, 0.45 for derived. Only curated or extracted rules at 0.7 or more can make a match likely_eligible.
What the rules cannot see
