Skip to content

REST API

The Databús REST API is served by the orchestrator service (Django + Daphne, ASGI) on port 8000 in development and behind Traefik at api.<domain> in production. It is the control plane for run management and the data-access layer for GTFS Schedule data.

OpenAPI / ReDoc: GET /api/docs/ — interactive documentation generated by drf-spectacular.

Schema download: GET /api/docs/schema/ — returns the OpenAPI 3.0 YAML that documents the realtime/telemetry submission interface (backend/api/realtime.yml). This is the same file /api/docs/ renders as ReDoc — get_schema serves it as a static file rather than a dynamically generated drf-spectacular schema.

Authentication: Token authentication (TokenAuthentication) via the Authorization: Token <key> header. Obtain a token with POST /api/login/. Not all endpoints require a token (GTFS Schedule reads are currently open).


Authentication

POST /api/login/

Authenticate an operator and receive a token.

Request:

{
    "username": "string",
    "password": "string"
}

Response 200:

{
    "token": "string",
    "operator_id": "string",
    "first_name": "string",
    "last_name": "string"
}

Response 400: {"error": "Usuario o contraseña incorrectos"}


Run lifecycle endpoints

POST /api/create-run/

Request creation of a new run. This is a synchronous multi-step call:

  1. Deserializes and validates the request body.
  2. Validates that vehicle_id and operator_id exist in the database.
  3. Creates a Run record in state REQUESTED.
  4. Applies VALIDATE_RUN (GTFS consistency check) → state VALIDATED.
  5. Applies INITIALIZE_RUN (writes Redis state, vehicle metadata) → state INITIALIZED.

Request body fields (validated by CreateRunSerializer, backend/api/serializers.py):

Field Type Required Notes
route_id string Yes GTFS route_id
trip_id string Yes GTFS trip_id
shape_id string Yes GTFS shape_id
direction_id int Yes GTFS direction_id, >= 0
schedule_relationship string Yes One of SCHEDULED, ADDED, UNSCHEDULED, CANCELED, DUPLICATED, DELETED
vehicle_id string Yes Must exist in operations.Vehicle
operator_id string Yes Must exist in operations.Operator

Note

Run has start_date and start_time model fields (backend/runs/models.py), but CreateRunSerializer does not currently accept either as input — they cannot be set through this endpoint and are left null on the created Run.

Response 200 (success):

{
    "status": "success",
    "run_id": "uuid",
    "run_lifecycle_state": "Initialized"
}

Response 400 — serialization or operational validation failure:

{
    "status": "error",
    "step": "serialization | operational_validation",
    "errors": {}
}

Response 422 — GTFS consistency check failed:

{
    "status": "error",
    "step": "gtfs_validation",
    "errors": {}
}


GET /api/runs/<uuid:run_id>/state/

Return the current lifecycle state of a run.

Response 200:

{
    "status": "success",
    "run_lifecycle_state": "Confirmed"
}

Response 404: {"status": "error", "errors": {"run_id": "Run not found"}}


POST /api/runs/<uuid:run_id>/update/

Request a lifecycle state transition for an existing run. This is the endpoint used by the operator UI and the simulator to send operator commands.

Request body:

Field Type Required Notes
event string Yes One of the allowed event values (see below)
details object No Additional payload merged into the event context

Allowed event values: RunUpdateSerializer.event is a ChoiceField over every member of RunLifecycleEvents (backend/runs/domain/lifecycle/events.py), so any of the values below pass serializer validation. Whether the request actually succeeds still depends on the run's current lifecycle state matching a transition for that event in backend/runs/domain/lifecycle/transitions.py — an event with no matching (from_state, event) transition, or one whose guards fail, returns a 422.

Event string Meaning Typical caller
validate_run GTFS consistency check Internal (create-run flow)
initialize_run Writes Redis state Internal (create-run flow)
run_rejected Reject/cancel during registration Internal (create-run flow on validation failure)
run_confirmed_by_operator Operator confirms they are ready Operator UI
cancel_run Cancel after confirmation, before/while tracking Operator UI
run_tracking_started Vehicle telemetry confirms tracking Usually detected; can be sent manually
run_started Vehicle confirmed moving Usually detected; can be sent manually
run_tracking_lost Telemetry has gone stale Usually detected (scan_stale_runs); can be sent manually
run_interrupted Manual interrupt — run ended unexpectedly Operator UI
run_short_turned Manual short-turn — vehicle turned around early Operator UI
run_completed Run ended successfully (manual or automatic) Operator UI or detection layer
run_tracking_restored Telemetry resumed after run_tracking_lost Usually detected; can be sent manually
run_tracking_expired Grace period after run_tracking_lost exceeded Usually detected (scan_stale_runs)

run_requested is also a valid enum value but has no entry in the transition table, so sending it here always returns 422 — a run only reaches Requested via POST /api/create-run/'s record creation, not via process_event.

Note

run_completed is the event string for both manual and automatic completion — it is a fact (something that happened), not a command. The REST endpoint accepts it as a command in the operator-triggered path; the detection layer fires it automatically in the telemetry-driven path.

Response 200 (success):

{
    "status": "success",
    "run_lifecycle_state": "Completed"
}

Response 400 — invalid event name or serialization error.

Response 404 — run not found.

Response 422 — FSM guard rejected the transition.


GET /api/runs/<uuid:run_id>/history/

Return the ordered FSM transition audit log for a run — every (event, from_state, to_state) attempt processed through RunLifecycleService.process_event, whether or not its guards passed.

Note

The run's initial Requested state is set by Run.objects.create(...) (a model field default), not by dispatching a run_requested event through process_event — so run_requested never appears as a transition here. The first entry for a run created via POST /api/create-run/ is its validate_run attempt.

Response 200:

{
    "run_id": "uuid",
    "transitions": [
        {
            "event": "validate_run",
            "from_state": "Requested",
            "to_state": "Validated",
            "timestamp": "2026-06-19T12:00:00+00:00",
            "actions": {},
            "guards": {
                "is_gtfs_valid": true,
                "is_trip_available": true,
                "is_vehicle_available": true,
                "is_operator_available": true
            }
        }
    ]
}


Operations endpoints

CRUD ViewSets for operational domain entities. All require token authentication unless noted.

Endpoint prefix Model Notes
/api/company/ Company Token auth currently commented out. CompanySerializer is a HyperlinkedModelSerializer with fields = "__all__" — the response is keyed by url (not a bare id), and linked_agency (the M2M to Agency) serializes as a list of hyperlinks
/api/operator/ Operator
/api/vehicle/ Vehicle Filterable by company
/api/data-provider/ DataProvider
/api/equipment/ Equipment POST returns {"id": ...}
/api/equipment-log/ EquipmentLog Filterable by equipment, data_provider, vehicle

Telemetry record endpoints

Historical GTFS-RT entity records. All require token authentication.

Endpoint prefix Model
/api/position/ runs.Position
/api/stop-status/ runs.VehicleStopStatus
/api/occupancy/ runs.OccupancyStatus
/api/congestion/ runs.CongestionLevel

GTFS Schedule endpoints

Read-only schedule data. Token authentication is currently not enforced.

Endpoint Filterable by
GET /api/agency/ agency_id, agency_name
GET /api/stops/ stop_id, stop_code, stop_name, stop_lat, stop_lon, stop_url
GET /api/geo-stops/ stop_id, location_type, zone_id, parent_station, wheelchair_boarding
GET /api/routes/ route_type, route_id
GET /api/trips/ shape_id, direction_id, trip_id, route_id, service_id
GET /api/stop-times/ trip_id, stop_id
GET /api/shapes/ shape_id
GET /api/geo-shapes/ shape_id
GET /api/calendars/ service_id
GET /api/calendar-dates/ service_id
GET /api/fare-attributes/ fare_id
GET /api/fare-rules/ route_id, origin_id, destination_id
GET /api/feed-info/ feed_publisher_name

Auxiliary GTFS endpoints

Endpoint Parameters Purpose
GET /api/service-today/ ?date=YYYY-MM-DD (optional) Returns active service_id list for a date
GET /api/which-shapes/ ?route_id= Returns GeoShapes for a route
GET /api/find-trips/ ?route_id=&service_id=&shape_id= Returns trips with start times and run lifecycle states

which-shapes and find-trips back the run-registration UI cascade (pick a route → its shapes → a candidate trip). Commit 5489fe4 repaired both: the views previously queried FK/field names that didn't exist on the models (RouteStop.route/.shape instead of linked_route/linked_shape; TripTime.trip_time instead of departure_time), so neither endpoint had ever worked.

GET /api/which-shapes/?route_id=

WhichShapesView looks up the current Feed's Route for route_id, then the distinct GeoShapes reachable from it via RouteStop.linked_route → RouteStop.linked_shape. Returns [] if the route or current feed can't be resolved.

Response 200 (WhichShapesSerializer, one object per distinct shape):

[
    {
        "shape_id": "string",
        "shape_name": "string | null",
        "shape_desc": "string | null",
        "shape_from": "string | null",
        "shape_to": "string | null"
    }
]

GET /api/find-trips/?route_id=&service_id=&shape_id=

FindTripsView filters Trip by route_id/service_id/shape_id within the current feed, resolves each trip's earliest TripTime via the TripTime.linked_trip FK (not a bare trip_id string match, which could cross-match a same-numbered trip from a different feed), and tags each result with the matching Run's current lifecycle state for today's start_date — or "UNKNOWN" if no such run exists. All three query parameters are required; missing any returns 400.

Response 200 (FindTripsSerializer, one object per trip):

[
    {
        "trip_id": "string",
        "trip_time": "HH:MM:SS",
        "run_lifecycle_state": "Requested | Validated | ... | UNKNOWN",
        "direction_id": 0,
        "trip_headsign": "string"
    }
]

Response 400 (missing parameter):

{"error": "Todos los parámetros route_id, service_id, shape_id son requeridos"}


URL structure

All API endpoints are mounted at /api/ in backend/databus/urls.py. The router URL prefix comes first (e.g., /api/vehicle/), then the explicit hand-written paths (e.g., /api/create-run/, /api/runs/<id>/update/).

Source: backend/api/urls.py, backend/api/views.py.