← Developers

De Lijn, detours and stops.

De Lijn runs the buses and trams outside Brussels, and publishes several different API products that overlap confusingly. This page covers the one Stadiq actually reads — the Kern Open Data REST API — what its detour feed really contains, and where GTFS Realtime sits relative to it.

Which product carries what

Register at data.delijn.be and subscribe per product. Authentication is a single header, Ocp-Apim-Subscription-Key — it is an Azure API Management gateway, so a key for one product returns 401on another. That is worth saying plainly, because the error text says “invalid subscription key” and reads like your key is wrong when your subscription is simply narrower than you thought.

Kern Open Data (REST)
api.delijn.be/DLKernOpenData/api/v1. Stops, lines, timetables, and the /omleidingen detour feed. JSON, resource oriented, HAL-ish links arrays. Everything verified on this page comes from here.
GTFS Static
The scheduled network as a standard GTFS zip. Also distributed via transportdata.be.
GTFS Realtime
Documented at api.delijn.be/gtfs/v3/realtime, a protobuf feed in the standard GTFS-RT format. It needs its own subscription; Stadiq does not hold one, so nothing about its response shape on this page would be first-hand and none is asserted. If you need vehicle positions or per-trip delay, this is the product to ask for, and the standard GTFS-RT tooling applies.

The short version: GTFS Realtime tells you a vehicle is late. Kern Open Data tells you a route has been rerouted for the next eight months.For “is the tram still stopping outside my shop”, the second is the useful one.

Entities: the region code you need first

De Lijn partitions everything by entiteit, its five operating regions. Most stop and line paths take the entity number as the first path segment, so you need it before you can ask about anything.

request and response
curl -s -H "Ocp-Apim-Subscription-Key: $DELIJN_KEY" \
  "https://api.delijn.be/DLKernOpenData/api/v1/entiteiten"

# {"entiteiten":[{"entiteitnummer":"1","entiteitcode":"A","omschrijving":"Antwerpen", …

Entity 1 is Antwerpen. Verified against /entiteiten— worth confirming for yourself rather than inferring it from a stop id, because the numbering is not alphabetical and guessing wrong silently returns another region's data rather than an error.

Stops near a point, and the lines that serve them

request
curl -s -H "Ocp-Apim-Subscription-Key: $DELIJN_KEY" \
  "https://api.delijn.be/DLKernOpenData/api/v1/haltes/indebuurt/51.2194,4.4025?radiusInMeter=500"
response, 2026-08-19
{
  "haltes": [
    {
      "type": "DELIJN",
      "id": "102675",
      "naam": "Antwerpen Groenplaats",
      "afstand": 100,
      "geoCoordinaat": { "latitude": 51.2187, "longitude": 4.40158 },
      "links": [
        { "rel": "lijnrichtingen",
          "url": ".../haltes/1/102675/lijnrichtingen" },
        { "rel": "real-time",
          "url": ".../haltes/1/102675/real-time" },
        { "rel": "dienstregelingen",
          "url": ".../haltes/1/102675/dienstregelingen" }
      ]
    },
    …
  ]
}

Note the argument order: /haltes/indebuurt/{lat},{lng} is latitude first, the opposite of GIPOD's bbox and IRCELINE's near. Three sources, three conventions. Unlike IRCELINE's, this radius is real, and afstand gives you the distance in metres so you do not have to compute it.

The catch: the stop object carries id but not the entity number. The entity only appears inside the links URLs, embedded in the path. To follow a stop to its lines you either parse it out of the lijnrichtingen URL, or just request that URL directly — which is what the links are for.

request and response
curl -s -H "Ocp-Apim-Subscription-Key: $DELIJN_KEY" \
  "https://api.delijn.be/DLKernOpenData/api/v1/haltes/1/102675/lijnrichtingen"

# {"lijnrichtingen":[
#   {"entiteitnummer":"1","lijnnummer":"180","richting":"HEEN",
#    "omschrijving":"Antwerpen Groenplaats - Aartselaar", …}, …]}

The detour feed, as it actually is

/omleidingen is the interesting endpoint and the one with the most gap between what you would design and what you get. It is unscoped — one call returns every active detour across Flanders, and you filter client-side.

request
curl -s -H "Ocp-Apim-Subscription-Key: $DELIJN_KEY" -H "Accept: application/json" \
  "https://api.delijn.be/DLKernOpenData/api/v1/omleidingen"
one record, 2026-08-19
{
  "titel": "Wijzigen vertrekpunt Dorp naar Wildendries",
  "titelMeertalig": { "nederlands": "…", "frans": "…", "duits": "…", "engels": "…" },
  "omschrijving": "Periode van maandag 2 september 2024 tot het einde van de werken.",
  "omschrijvingMeertalig": { … },
  "periode": { "startDatum": "2024-09-02T00:00:00" },
  "lijnrichtingen": [
    { "entiteitnummer": "2", "lijnnummer": "875", "richting": "TERUG",
      "omschrijving": "Geraardsbergen - Aspelare - Haaltert - Aalst", "links": [ … ] }
  ],
  "haltes": [
    { "entiteitnummer": "2", "haltenummer": "206513",
      "halteToegankelijkheden": [], "hoofdHalte": null,
      "bedieningsTypes": null, "links": [ … ] }
  ],
  "referentieOmleiding": 0,
  "omleidingsDagen": []
}

Here is what a full pull looks like, counted rather than assumed:

measured across all 331 records
# GET /omleidingen, 2026-08-19
top-level keys          ["omleidingen", "links"]      # no "storingen"
records                 331
entities present        1, 2, 3, 4, 5                 # 1 = Antwerpen
records touching ent 1  90
referentieOmleiding = 0 331 of 331                    # not a usable id
haltes with coordinates 0 of all                      # entity + number only
lijnNummerPubliek       absent on every lijnrichting
periode.eindDatum       193 of 331                    # the rest are open-ended
line numbers            "001", "010", "875" …         # zero-padded, sometimes

What that means in practice

  • There is no id. referentieOmleiding was 0on all 331 records. If you need to track a detour across polls — to say “this is new since yesterday” — you have to synthesise a key from title, lines and start date, and accept that an edited title looks like a new detour.
  • Affected stops have no coordinates. Each entry in haltes is an entity number, a stop number and a links array. If you want to draw the affected stops on a map you must resolve each one through /haltes/{entity}/{number} — one request per stop, so cache them; stop locations barely change.
  • Line numbers are zero-padded, inconsistently. The detour feed returns "010" where a stop's lijnrichtingen returns "10". 53 of the distinct line numbers in this pull were zero-padded. Strip leading zeros on both sides before comparing, or your “does this detour affect a line that stops near me” test quietly matches nothing.
  • There is no severity field, and no type. Every record has the same shape whether it is a two-day works detour or a permanent route change. Severity is something you infer — from duration, from whether periode.eindDatum is present at all (it was absent on 138 of 331, meaning open-ended), from the text.
  • Dates are naive. periode.startDatum is "2024-09-02T00:00:00" with no zone. Interpret as Europe/Brussels; parsing as UTC shifts everything by an hour or two depending on the season.
  • Detours are long-lived.The first record in the feed started in September 2024 and is still open. “Active detour” is not a proxy for “something happened today”, the same trap as GIPOD's two-year end dates.
  • Text is available in Dutch, French, German and English via the titelMeertalig and omschrijvingMeertalig objects — though in this pull the four language variants of the title were frequently identical Dutch strings, so the English key is a place for a translation rather than a guarantee of one.

Filtering to what matters

331 detours across Flanders is not an answer to “is my tram affected”. Two filters get you there. First, entity: 90 of the 331 touched Antwerpen. Second, line overlap: collect the line numbers serving stops within a few hundred metres of the address, normalise the padding, and keep only detours touching one of them.

A widely-spaced address may have no stops in range at all, and then the line filter has nothing to work with. Decide deliberately which way that fails. Stadiq keeps every Antwerp detour rather than none, on the grounds that too much is recoverable and a silent empty list is not.

Licence and registration

De Lijn publishes under a free-reuse licence linked from the developer portal; read it against your use case, and attribute De Lijn where you show the data. Registration is self-service and immediate. We did not independently verify the published rate limits, so do not plan capacity around a number you read second-hand, this page included — check the portal.

How Stadiq uses it

Stadiq resolves the lines serving stops near an address, pulls /omleidingen, keeps Antwerp-entity detours touching one of those lines, drops anything across the Schelde from the address, and returns the result in the transit array of GET /api/v1/disruptions. See the API reference.

  • GIPODEvery permitted road work and public-domain occupation in Flanders, over an open OGC API Features service that needs no key.
  • IRCELINE air qualityBelgium's official air-quality measurements, over a 52°North SOS timeseries API. Open, and stranger than it looks.
  • Antwerp open dataWhat the city of Antwerp publishes, where the ArcGIS endpoints are, and how to query them as GeoJSON.
  • Stadiq CLIInstall the `stadiq` command, store a key, and read disruptions or a briefing from a terminal or a cron job.
  • Stadiq MCP serverThe same four endpoints as read-only agent tools, for Claude Desktop, Cursor or any MCP client.