Skip to content

Statistics and exports

To analyse the service over long periods, you do not walk the per-item endpoints: the API exposes bulk outputs, in two shapes.

Two shapes, two uses

Shape Path What it returns For what
Aggregate /stats/<domain> Values already computed over the period Dashboard, tracked indicator
Export /export/<domain> The detail, row by row Data warehouse, reprocessing

The choice follows the need: an aggregate saves you from recomputing what the API already knows how to do, an export gives the raw material for analyses the API does not provide.

The eight exports

These eight endpoints make up the datahub category, intended to feed a data warehouse.

Export Expected parameters
export/trips date (required)
export/trip-km start_date, end_date (required)
export/passenger-counts start_date, end_date (required)
export/punctuality start_date (required), end_date, and optional filters (event: arrival or departure, stop_id, gtfs_id, driver_id, route_id)
export/trip-tracking start_date, end_date (required)
export/drivers archived (optional)
export/vehicles statuses (optional)
export/alerts none

Three points to read carefully before writing a client:

  • The exports do not share the same signature. Some bound a period, one takes a single date, three take none. There is no generic call to write once for all eight.
  • Write dates in the compact YYYYMMDD form. It is the only one all five dated endpoints accept. punctuality and trip-tracking also tolerate YYYY-MM-DD, but trips, trip-km and passenger-counts reject it with a validation error.
  • The perimeter follows the key's rights. A key restricted to certain teams exports fewer rows than expected, with nothing in the response to say so.

All eight endpoints return CSV. The spec nevertheless declares application/json for export/trips: that is a declaration defect, the content served is indeed CSV.

The aggregates

The same domains exist in computed form, under /stats/. They bound the period with start_date and end_date in the YYYYMMDD format, except stats/punctuality, which takes from and to as Unix timestamps in seconds. A YYYYMMDD date passed to from or to is rejected there with a 422 error, as is any value before January 1, 2015.

Endpoint Purpose
GET /api/v4/groups/<group_id>/stats/punctuality Punctuality aggregated over the period
GET /api/v4/groups/<group_id>/stats/trip-km Kilometres covered
GET /api/v4/groups/<group_id>/stats/passenger-counts Ridership
GET /api/v4/groups/<group_id>/stats/vehicles-used Vehicles committed

On-demand route details

GET /api/v4/groups/<group_id>/export/on-demand-routes/details returns a semicolon-delimited CSV with one row per passenger count, sorted by date then ascending passed_at, plus the rows described below for service sessions and kilometres. start_date and end_date are required in YYYYMMDD format and filter inclusive service dates. route_id and teams are optional; the reader's team restrictions apply as they do to statistics.

The columns, in order, are date, passed_at, route_id, route_short_name, stop_id, stop_name, vehicle, boarding, alighting, tranche_horaire, route_long_name, stop_lat, stop_lon, service_id, service_start, service_end, service_km. date is the service date in YYYY-MM-DD format; passed_at is an ISO 8601 timestamp with the group's timezone offset. Multiple counts at the same stop or instant remain separate rows. tranche_horaire is the local hour of passed_at, lower bound included (08:00-09:00). stop_lat, stop_lon and route_long_name are those of the route as configured today, and the coordinates are empty for a removed stop.

A service session without any count gets a row of its own (empty stop and time, 0 boarding and alighting). service_id, service_start and service_end (local time, empty while the service is open) identify the session of each row. service_km holds the kilometres of the session for a local day, on the first row of that session for that day and empty on the others, so that its sum is never counted twice; a session crossing midnight carries its kilometres on each of the two days, and a day of kilometres with no count gets its own row. Kilometres recorded before sessions were identified come as rows with an empty service_id, one per day, route and device. A session started before the period is listed only through the counts and kilometres it has inside it. An empty period produces only the header.

The vehicle belongs to the service of the same device and route containing the passage: inclusive start, exclusive end; a service without an end remains open, even if it started several days before the exported period. When services overlap, the most recently started one wins. vehicle is empty without a matching service or known vehicle; otherwise it shows the licence plate and fleet number, keeping only the filled parts. A stop removed from its route retains its count with an empty stop_name.

The sums of service_km, boarding and alighting by date and route match export/on-demand-routes with the same filters. Both exports require the same statistics read roles and the group's ON_DEMAND and DEVICE_EVENT_SOURCING features. The download button in Op ships separately.

Asynchronous exports

For long periods, an export can be prepared in the background instead of being streamed in the response: POST /api/v4/groups/<group_id>/exports creates the job and answers 202 with a job_id; GET /api/v4/groups/<group_id>/exports/<job_id> returns its state (pending, running, succeeded or failed) and, once succeeded, a signed download URL, renewed on each read. A job is only visible to the person who requested it and disappears after thirty days, together with its file.

The request body carries export_type and params. The file produced is the same CSV as the matching synchronous export, with the same access rules: a role the synchronous endpoint refuses makes the job fail, with no file.

export_type params Matching synchronous export
trip_km start_date, end_date (required), files, trips_over_days export/trip-km
passenger_counts start_date, end_date (required) export/passenger-counts
punctuality start_date (required), end_date, event (arrival or departure), stop_id (list), gtfs_id, driver_id, route_id export/punctuality

An invalid parameter is rejected with a 422 error, without creating a job. The synchronous endpoints remain available and unchanged.

Querying the past

An export covers a closed period. To reconstruct an elapsed day in detail — events reported, passings observed, GTFS then in force — query the history instead.

The reminder from the GTFS reference page fully applies here: data from six months ago is interpreted against the GTFS in force at that date, not against today's.

Good practice

  • Split long periods. An export over a whole year produces a large response and a long request; chaining monthly slices is safer and resumes after a failure.
  • Do not set a short timeout. These responses are streamed, and a wide export takes time to start.
  • Replaying an export has no side effect. These endpoints are read-only: when in doubt about a slice, asking for it again costs only the transfer.

The matching code examples, in the language selected in the header, are on the Collection examples page.