Skip to content
Subsido

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

NodeShapeResult
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.

fieldTyperequest_pathMeaning
company.regionslist of regionscompany.regionRegions where the company has an establishment (its seat or an operating unit).
company.provinceprovincecompany.provinceThe province it is established in.
company.sizesize bandcompany.sizemicro, small, medium or large.
company.employeesnumbercompany.employeesStaff (FTE).
company.turnover_eurnumbercompany.turnover_eurAnnual turnover.
company.balance_sheet_eurnumbercompany.balance_sheet_eurBalance sheet total.
company.nacelist of codescompany.naceNACE-BEL codes, dots removed.
company.legal_formlegal formcompany.legal_formbv, nv, cv, vzw, eenmanszaak, ...
company.applicant_typeapplicant typecompany.applicant_typecompany, self_employed, non_profit, ...
company.age_yearsnumbercompany.founded_onCompleted years since it was founded.
company.has_legal_personalitybooleancompany.has_legal_personalityWhether it is a legal person.
company.de_minimis_received_eurnumbercompany.de_minimis_received_eurDe minimis aid over the last three years.
company.in_difficultybooleancompany.in_difficultyAn undertaking in difficulty.
project.topicslist of topicsproject.topicsWhat the project is about.
project.budget_eurnumberproject.budget_eurThe project’s cost.
project.startedbooleanproject.startedWhether it has already started (the incentive effect).
project.regionregionproject.regionWhere the project takes place.
project.cost_typeslist of cost typesproject.cost_typesThe costs to be supported.
project.duration_monthsnumberproject.duration_monthsHow long it lasts.
project.partnersnumberproject.partnersIndependent 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.

opvaluePasses when
eq, instring or listThe 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_instring or listNo element of the fact is one of the values.
contains_anylistA list fact shares at least one element with the value.
contains_alllistEvery element of the value is in the fact.
contains_nonelistThe fact shares no element with the value.
gte, gt, lte, ltnumberThe numeric comparison holds.
between[min, max]min ≤ fact ≤ max. Anything other than exactly two bounds is unknown.
is_true, is_falseignoredThe boolean fact is true, or false.
nace_anylist of codesAt 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_nonelist of codesNone 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:

FieldMeaning
methodstructuredhtml_sectiontext_patternsource_categorykeywordscuratedderived
source_urlThe page the rule was read from.
source_sectionThe heading of the section it was read from (“Wie komt in aanmerking?”, “Art. 1:24 WVV”).
review_statusauto: produced from a structured field. unreviewed: found in the text by a pattern. verified: checked against the official page, on reviewed_at.
reviewed_atYYYY-MM-DD, for verified rules.
noteWhat the rule cannot see, or why it is written as it is.

Where rules come from

  • From structured fields: a regional measure gets a region rule (contains_any on company.regions), a size restriction a size rule, a list of applicant types an applicant_type rule, sector lists nace and nace_excluded rules. Their review_status is auto.
  • From patterns in the text (a maximum or minimum company age, for instance): method is text_pattern and review_status is unreviewed.
  • Written from the official page: method is curated and review_status is verified. 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

Rules cover what can be stated about a company and a project. They do not see linked enterprises, a company’s aid history beyond what you send, whether an authority still has budget, or conditions only a file review can check. That is why the strongest status is “likely”, and why every match links the official page.