Skip to content
Subsido

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&region=flanders" \  -H "Authorization: Bearer sb_live_..."
ParameterMeaning
sinceRFC 3339 or YYYY-MM-DD (the start of that Brussels day): events at or after it. At most 400 days back; versions go further.
typeComma 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.
subsidyOne measure’s id (not its slug).
sourceComma list of source ids. Also matches the source’s own freshness events.
regionComma list of regions: events about measures there, and about national and EU measures, which apply everywhere. Source events pass.
limit, cursorPage 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"
}
FieldMeaning
idThe event’s id, chg_…. The same id is the webhook delivery’s id.
seqMonotonic across the feed. The cursor is built on it.
typeThe primary event type.
categoriesEvery category the version touched. A version that changed status and deadline together is type subsidy.status_changed with both categories.
subject, subject_idsubsidy and the measure’s id, or source and the source’s id.
versionThe 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.
summaryEnough 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_atWhen the change was recorded, in Brussels time.

Event types and categories

TypeWhen
subsidy.createdA measure appears for the first time.
subsidy.status_changedThe status field changed: opened, closed, paused, budget exhausted.
subsidy.deadline_changedAnything under application_window: a deadline, the opening date, rolling, budget exhaustion.
subsidy.funding_changedAnything under funding: rates, amounts, caps, the programme budget.
subsidy.eligibility_changedAnything under eligibility except topics: applicant types, sizes, sectors, rules, cost types.
subsidy.updatedAnything else: titles, summaries, topics, links, the legal basis.
subsidy.closing_soonAn 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.removedThe source no longer lists the measure. It stops appearing in lists unless you pass include_withdrawn.
source.staleA source crossed from fresh or delayed into stale, unavailable or unreadable.
source.recoveredIt came back.
match.created, match.status_changed, match.removedWatchlist 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

  • field is 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_at and provenance.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.

JavaScript
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

Webhooks push every event to your endpoint as it happens, with the same filters, so you read the feed only to catch up.

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

causeMeaning
source_changeThe source published something different.
date_passedThe hourly sweep: a deadline or an opening date passed.
reprocessWe normalised the source’s last reading again, after a fix to how it is read.
withdrawnThe source stopped listing the measure (it emits subsidy.removed).
closed_at_sourceThe source stopped listing it, and for that source a missing item means the call closed.
restoredA measure the source had stopped listing is back.
curation, mergeReserved 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.