Transportation — entry 034 of 69

Transport for Belgium

Verified Jul 2026

Independent, community-run, free API for Belgian rail travel, mirroring NMBS/SNCB data as live departure boards, connections, and vehicle compositions. Built and maintained as an open-source hobby project, it requires no API key and returns either JSON or XML depending on the requested format.

iRail is a free, independent, keyless JSON/XML API for Belgian rail (NMBS/SNCB) data — live liveboards, connections, vehicle compositions, and disturbances — maintained by a community of volunteers rather than the railway itself. The API is available at https://api.irail.be, and its own docs describe it as "an attempt to make the railway time schedules in Belgium easily available for anyone."

railtransitbelgiumopen-sourcecommunity
AuthenticationNone requiredCall it straight away — no key, no signup.
HTTPSSupportedTraffic is encrypted in transit.
CORSEnabledCallable directly from browser JavaScript.
PricingFreeNo paid tier — free for the documented use case.
FormatsJSON, XMLResponses can be requested as JSON or XML.

GreatAPIs Score

Score94out of 100
Authentication25/25No authentication required
Pricing20/20Free to use
Docs14/20Documentation URL provided
Formats15/15Supports 2 response formats
Freshness20/20Verified within 6 months

Embed this badge

Scored 94 on greatapis.com
<a href="https://greatapis.com/api/transport-for-belgium/"><img src="https://greatapis.com/badge/transport-for-belgium.svg" alt="Scored 94 on greatapis.com"></a>

Auth quickstart

  1. No API key required — requests are public and keyless. The docs' "Best practices" section asks (without enforcing it) that callers set a descriptive User-Agent identifying the application and a contact method, in the form `<application name>/<application version> (<website>; <mail>)`, e.g. `irail/1.2.0 (irail.be; hello@irail.be)` — so that if something goes wrong, the maintainers can reach the developer instead of just blocking the IP outright.
Stored keyNo key stored

Your key is stored only in this browser (localStorage) and sent directly to the API — never to greatapis.

Get a live departure board for a station

GEThttps://api.irail.be/v1/liveboard?station=Brussels-South&format=json

User-Agent: your-app/1.0 (yourdomain.example; you@yourdomain.example)

200 application/json;charset=UTF-8

{"version":"1.4","timestamp":"1785068821","station":"Brussels-South/Brussels-Midi","stationinfo":{"@id":"http://irail.be/stations/NMBS/008814001","id":"BE.NMBS.008814001","name":"Brussels-South/Brussels-Midi","locationX":"4.336531","locationY":"50.835707","standardname":"Brussel-Zuid/Bruxelles-Midi"},"departures":{"number":"43","departure":[{"id":"0","station":"Paris Nord","stationinfo":{"@id":"http://irail.be/stations/NMBS/008727100","id":"BE.NMBS.008727100","name":"Paris Nord","locationX":"2.333333","locationY":"48.866667","standardname":"Paris Nord"},"time":"1785068160","delay":"0","canceled":"0","left":"0","isExtra":"0","vehicle":"BE.NMBS.EUR9346","vehicleinfo":{"name":"BE.NMBS.EUR9346","shortname":"EUR 9346","number":"9346","type":"EUR","locationX":"0","locationY":"0","@id":"http://irail.be/vehicle/EUR9346"},"platform":"6","platforminfo":{"name":"6","normal":"0"},"occupancy":{"@id":"http://api.irail.be/terms/unknown","name":"unknown"},"departureConnection":"http://irail.be/connections/8814001/20260726/EUR9346"}]}}

The `departure` array is truncated to its first element — the live board returned 43 departures (`"number":"43"`). The departure object itself is unmodified: every key it returns is shown, including the per-departure `stationinfo` and `departureConnection`, and all 7 `vehicleinfo` keys.

Developer reference

Base URLhttps://api.irail.be/v1
Rate limit

3 requests/second per IP, with a burst allowance of 5

Gotchas & limits

  • The unversioned legacy path (`GET /liveboard/?station=...`, no `/v1`) still works but returns a `303 See Other` redirecting to the versioned `/v1/liveboard` — confirmed live in this run. Call `/v1/...` directly rather than following a redirect on every request.
  • Almost every numeric-looking field comes back as a JSON **string**, not a number — in the response above, `"delay":"0"` (seconds), `"number":"43"` (a count), `"timestamp":"1785068821"` (Unix epoch), and even `"locationX"`/`"locationY"` coordinates are all quoted strings. Parse with `parseInt`/`Number()` before doing arithmetic on them.
  • CORS is enabled, and the value of `Access-Control-Allow-Origin` depends on your request: with no `Origin` header the API sends the blanket `*` (which is also what its docs' response tables document), but when a request does carry an `Origin` it echoes that exact origin back instead — both probed live in this run (`curl` alone → `*`; `curl -H "Origin: https://example.com"` → `https://example.com`). Browser calls work from any origin either way, but don't assume a static `*` if you're asserting on the header programmatically.
  • Requests are capped at 3 per second per source IP, with a burst allowance of 5 (so bursts of 8 requests in 1 second, or 15 in 3 seconds, are tolerated before you must drop back under 3/sec) — exceeding it returns `429`. Per the docs, an IP that sends excessive requests **without** a descriptive User-Agent set risks being blocked without any prior warning; one that does set a User-Agent gets contacted first.
  • Omitting `format` doesn't error and doesn't default to JSON — a live `GET /v1/liveboard?station=Brussels-South` with no `format` returned `200` with `Content-Type: application/xml;charset=UTF-8`. Pass `format=json` explicitly on every request (or `format=xml` deliberately, which is equally supported).