Staying current
Changes and versions
Government portals answer what applies now. The change feed answers what changed since you last looked: a new call, a deadline moved from 1 to 15 October, a maximum raised, a size rule that now admits large companies.
Versions and events
Every time a measure’s content changes, a new version is written, with the field-level differences from the one before. Checking a source again without a change writes nothing, so a version is always a real change. Each version produces one event in the change feed, typed by the most significant thing that changed and listing every category it touched. Versions are kept for ever; events are kept for 400 days.
The change feed
Developer and aboveGET /v1/changes needs the change_feed capability.
curl "https://api.subsido.be/v1/changes?since=2026-09-01&type=subsidy.created,subsidy.deadline_changed®ion=flanders" \ -H "Authorization: Bearer sb_live_..."| Parameter | Meaning |
|---|---|
| since | RFC 3339 or YYYY-MM-DD (the start of that Brussels day): events at or after it. At most 400 days back; versions go further. |
| type | Comma list of event types. An event matches when its primary type or any of its categories is in the list. match.* types are refused: they are never in the public feed. |
| subsidy | One measure’s id (not its slug). |
| source | Comma list of source ids. Also matches the source’s own freshness events. |
| region | Comma list of regions: events about measures there, and about national and EU measures, which apply everywhere. Source events pass. |
| limit, cursor | Page size and position; see pagination. |
Events come oldest first, in seq order. The page is { data, pagination, meta }, and meta.latest_seq is the newest seq in the whole feed, so you can tell how far behind you are.
{
"id": "chg_06f8s2k1c9t4r7m3q5v0x8z2yw",
"seq": 18422,
"type": "subsidy.status_changed",
"categories": [
"subsidy.status_changed",
"subsidy.deadline_changed"
],
"subject": "subsidy",
"subject_id": "eu:HORIZON-EIC-2026-ACCELERATOR-01",
"version": 4,
"changes": [
{
"field": "status",
"before": "open",
"after": "closed"
},
{
"field": "application_window.closes_at",
"before": "2026-10-01T17:00:00+02:00",
"after": "2026-09-20T17:00:00+02:00"
}
],
"summary": {
"title": {
"nl": null,
"fr": null,
"en": "EIC Accelerator",
"de": null
},
"status": "closed",
"instrument_type": "grant",
"issuer": "European Commission",
"closes_at": "2026-09-20T17:00:00+02:00",
"regions": [],
"topics": [
"innovation",
"growth_scaleup"
],
"url": "https://ec.europa.eu/info/funding-tenders/opportunities/portal/screen/opportunities/topic-details/HORIZON-EIC-2026-ACCELERATOR-01",
"source_id": "eu_funding_tenders"
},
"occurred_at": "2026-09-27T06:12:40+02:00"
}| Field | Meaning |
|---|---|
| id | The event’s id, chg_…. The same id is the webhook delivery’s id. |
| seq | Monotonic across the feed. The cursor is built on it. |
| type | The primary event type. |
| categories | Every category the version touched. A version that changed status and deadline together is type subsidy.status_changed with both categories. |
| subject, subject_id | subsidy and the measure’s id, or source and the source’s id. |
| version | The measure’s version this event produced, or null for events that are not versions (closing soon, source events). |
| changes[] | { field, before, after } for every leaf that differs. See below. |
| summary | Enough to act without a second request: title (every language), status, instrument_type, issuer, closes_at, regions, topics, the official url and source_id. Closing-soon events add days_left and threshold_days; source events carry freshness and previous. |
| occurred_at | When the change was recorded, in Brussels time. |
Event types and categories
| Type | When |
|---|---|
| subsidy.created | A measure appears for the first time. |
| subsidy.status_changed | The status field changed: opened, closed, paused, budget exhausted. |
| subsidy.deadline_changed | Anything under application_window: a deadline, the opening date, rolling, budget exhaustion. |
| subsidy.funding_changed | Anything under funding: rates, amounts, caps, the programme budget. |
| subsidy.eligibility_changed | Anything under eligibility except topics: applicant types, sizes, sectors, rules, cost types. |
| subsidy.updated | Anything else: titles, summaries, topics, links, the legal basis. |
| subsidy.closing_soon | An open measure’s deadline is 14 days away, and again at 3 days. Not a version: an hourly sweep emits it once per measure, deadline and threshold. |
| subsidy.removed | The source no longer lists the measure. It stops appearing in lists unless you pass include_withdrawn. |
| source.stale | A source crossed from fresh or delayed into stale, unavailable or unreadable. |
| source.recovered | It came back. |
| match.created, match.status_changed, match.removed | Watchlist events, for the owner’s webhooks only. See watchlists. |
A version’s primary type is the most significant category it touched, in the order status, deadline, funding, eligibility, updated. Filter or subscribe on a category, not only on the primary type, or you will miss the deadline change that came with a status change.
Field-level differences
fieldis a dotted path to a leaf:status,application_window.closes_at,funding.amount_max,titles.nl. Arrays are compared whole, so a changed list of regions is one change with the old and new lists.- The rule list is compared rule by rule on their ids:
eligibility.rules[age_max]with the rule before and after (null when it was added or removed). A change of a rule’s provenance alone, such as a new review date, is not a change of the rule. - Bookkeeping is versioned but never reported:
provenance.last_checked_at,provenance.content_hash,provenance.first_seen_atandprovenance.fields. A version whose only differences are bookkeeping produces no event.
Dates passing
Every hour the worker normalises every current record again against the time now. A call whose deadline has passed becomes closed, a forthcoming one that has opened becomes open, and closes_at moves to the next deadline of a multi-stage call. Each of those is a new version with cause date_passed and a normal event, typically subsidy.status_changed or subsidy.deadline_changed. The same hourly sweep emits subsidy.closing_soon.
Following the feed
Make the first call with since (or without it, from the oldest event kept), then follow next_cursor. Every page has one, the last page included: when has_more is false you have caught up, and the same cursor is where the next poll starts. Store it with the work it covers, and a restart picks up exactly where it stopped.
Nothing is skipped and nothing is repeated, even though an import run commits many events at once and runs can overlap: an event gets its place in the feed (its seq) only after it has committed, so an event that commits late comes after everything you have already read. No overlap window and no list of seen ids is needed. seq is the order of the feed; occurred_at is when the change was observed and can be a little older than an event before it.
let cursor = await loadCursor(); // null on the very first run
async function poll() {
for (;;) {
const url = new URL("https://api.subsido.be/v1/changes");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
else url.searchParams.set("since", "2026-09-01");
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.SUBSIDY_API_KEY}` },
});
const page = await res.json();
if (!res.ok) throw new Error(page.error.code);
for (const e of page.data) await handle(e);
cursor = page.pagination.next_cursor; // always set
await saveCursor(cursor);
if (!page.pagination.has_more) return; // caught up; poll again later
}
}Or let us call you
Versions and history
Pro and aboveVersions and as_of need the history and as_of capabilities.
GET /v1/subsidies/{id}/versions lists a measure’s versions, newest first: { version, valid_from, valid_to, cause, content_hash, changes }. GET /v1/subsidies/{id}/versions/{version} returns the record as it was, and GET /v1/subsidies/{id}?as_of= the version current at an instant. Examples are on searching measures.
Why a version was written
| cause | Meaning |
|---|---|
| source_change | The source published something different. |
| date_passed | The hourly sweep: a deadline or an opening date passed. |
| reprocess | We normalised the source’s last reading again, after a fix to how it is read. |
| withdrawn | The source stopped listing the measure (it emits subsidy.removed). |
| closed_at_source | The source stopped listing it, and for that source a missing item means the call closed. |
| restored | A measure the source had stopped listing is back. |
| curation, merge | Reserved for a person’s correction and for two records merged into one. Old ids of a merged record keep resolving; see merged_from. |
Versions and the change feed turn “what is open” into “what changed, when, and what did we know on the day we advised”. See pricing.
