Realtime/SL

List vehicles

Vehicle positions, freshest first, with route and stop names joined on.

GET /api/vehicles

Sorted by timestamp descending; vehicles without one sort last, then by id, so paging is stable.

Pass format=geojson to get a FeatureCollection instead of the usual envelope — see the second example below. That form drops vehicles with no position and reports how many in meta.omittedWithoutPosition rather than shrinking the result set silently.

Try it

Runs from your browser against the API in .

Query

GET
curl equivalent
 

Parameters

Query

ParameterTypeDefaultDescription
routeIdstring

GTFS route id. Served from an index, so it is the cheapest filter here. Nearly every value you can pass was backfilled by the static join — check trip.routeIdSource.

tripIdstring

Exact match on trip.tripId.

stopIdstring

Exact match on the vehicle's current stopId.

Matches nothing in this capture. Not one of the 1,116 vehicles carries a stopId — the SL vehicle feed omits it entirely.

statusenum

Where the vehicle is relative to stopId. Case-insensitive.

INCOMING_ATSTOPPED_ATIN_TRANSIT_TO

Only IN_TRANSIT_TO matches, and it matches all 1,116 — because that is the spec default the API applies when the feed omits the field, which it always does here. This is a default, not an observation: it does not mean every vehicle is genuinely between stops.

occupancyenum

How full the vehicle is.

EMPTYMANY_SEATS_AVAILABLEFEW_SEATS_AVAILABLESTANDING_ROOM_ONLYCRUSHED_STANDING_ROOM_ONLYFULLNOT_ACCEPTING_PASSENGERSNO_DATA_AVAILABLENOT_BOARDABLE

Matches nothing in this capture. Zero of 1,116 vehicles report occupancy. Absent is not EMPTY — see Absent is not zero.

congestionenum

Traffic congestion around the vehicle.

UNKNOWN_CONGESTION_LEVELRUNNING_SMOOTHLYSTOP_AND_GOCONGESTIONSEVERE_CONGESTION

Matches nothing in this capture. Zero of 1,116 vehicles report congestion.

bboxbbox

Bounding box as minLon,minLat,maxLon,maxLat — GeoJSON axis order, longitude first. Vehicles with no position never match. Boxes crossing the antimeridian are rejected; split them client-side.

formatenumjson

geojson swaps the envelope for a FeatureCollection.

jsongeojson

limitinteger50

Page size, 1–1000. Values outside that range are a 400 rather than a silent clamp.

offsetinteger0

Rows to skip. Compare against meta.total to know when you have them all.

Response

The standard list envelope: data plus meta with count, total, limit, offset, feedTimestamp and loadedAt.

GET /api/vehicles?limit=2
curl "$API_BASE/api/vehicles?limit=2"
200 · application/json
{
  "data": [
    {
      "id": "9031001001501695",
      "vehicle": {
        "id": "9031001001501695"
      },
      "trip": {
        "tripId": "14010000703221662",
        "routeId": "9011001087300000",
        "directionId": 0,
        "scheduleRelationship": "SCHEDULED",
        "routeIdSource": "static",
        "route": {
          "routeId": "9011001087300000",
          "agencyId": "505000000000000001",
          "shortName": "873",
          "type": 700
        }
      },
      "position": {
        "latitude": 59.242779,
        "longitude": 18.223082,
        "bearing": 270,
        "speed": 10.6
      },
      "currentStatus": "IN_TRANSIT_TO",
      "timestamp": {
        "epoch": 1777288959,
        "iso": "2026-04-27T11:22:39.000Z"
      }
    },
    {
      "id": "9031001007000472",
      "vehicle": {
        "id": "9031001007000472"
      },
      "position": {
        "latitude": 59.332401,
        "longitude": 18.06319,
        "bearing": 249,
        "speed": 0
      },
      "currentStatus": "IN_TRANSIT_TO",
      "timestamp": {
        "epoch": 1777288959,
        "iso": "2026-04-27T11:22:39.000Z"
      }
    }
  ],
  "meta": {
    "count": 2,
    "total": 1116,
    "limit": 2,
    "offset": 0,
    "feedTimestamp": "2026-04-27T11:22:38.000Z",
    "loadedAt": "2026-08-07T12:01:42.748Z"
  }
}

As GeoJSON

format=geojson with a bounding box over central Stockholm. Note the different envelope and the omittedWithoutPosition count.

GET /api/vehicles?bbox=17.9,59.30,18.15,59.36&format=geojson&limit=2
curl "$API_BASE/api/vehicles?bbox=17.9,59.30,18.15,59.36&format=geojson&limit=2"
200 · application/json
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          18.06319,
          59.332401
        ]
      },
      "properties": {
        "id": "9031001007000472",
        "vehicle": {
          "id": "9031001007000472"
        },
        "currentStatus": "IN_TRANSIT_TO",
        "timestamp": {
          "epoch": 1777288959,
          "iso": "2026-04-27T11:22:39.000Z"
        },
        "bearing": 249,
        "speed": 0
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
          18.031305,
          59.347157
        ]
      },
      "properties": {
        "id": "9031001001004006",
        "vehicle": {
          "id": "9031001001004006"
        },
        "trip": {
          "tripId": "14010000635931335",
          "routeId": "9011001000600000",
          "directionId": 1,
          "scheduleRelationship": "SCHEDULED",
          "routeIdSource": "static",
          "route": {
            "routeId": "9011001000600000",
            "agencyId": "505000000000000001",
            "shortName": "6",
            "type": 700
          }
        },
        "currentStatus": "IN_TRANSIT_TO",
        "timestamp": {
          "epoch": 1777288958,
          "iso": "2026-04-27T11:22:38.000Z"
        },
        "bearing": 140,
        "speed": 0
      }
    }
  ],
  "meta": {
    "count": 2,
    "total": 308,
    "limit": 2,
    "offset": 0,
    "omittedWithoutPosition": 0,
    "feedTimestamp": "2026-04-27T11:22:38.000Z",
    "loadedAt": "2026-08-07T12:01:42.748Z"
  }
}

Errors

400
{ "error": "request_failed", … }

An enum filter got a value outside its list, or `bbox` was malformed, or `limit`/`offset` were out of range.