Transport

Trips, routes, vehicles, positions and the geo services behind them.

42 endpoints

POST /v3/app/geo/route #
bearer driver_app beneficiary_app device credential

point-to-point (or multi-via) polyline + totals.

Request body

FieldTypeConstraints
stopsobject[]requiredmin items 2 · max items 25

Response

Returns { "data": … }; this endpoint's payload is not yet pinned to a named shape.

GET /v3/app/geo/snap #
bearer driver_app beneficiary_app device credential

nearest road point for marker placement.

Query parameters

NameTypeConstraints
latnumberrequiredmin -90 · max 90
lngnumberrequiredmin -180 · max 180

Response

Returns { "data": … }; this endpoint's payload is not yet pinned to a named shape.

POST /v3/app/geo/trip #
bearer driver_app beneficiary_app device credential

optimal dropoff order. The first stop is the driver (OSRM `source=first`); `destination=last` anchors the end at the final stop (the school run).

Request body

FieldTypeConstraints
stopsobject[]requiredmin items 2 · max items 25
destinationstringoptionalany last
roundtripbooleanoptional

Response

Returns { "data": … }; this endpoint's payload is not yet pinned to a named shape.

POST /v3/app/trip #
bearer driver_app beneficiary_app transport.trip_manage device credential

Request body

FieldTypeConstraints
idstring (uuid)required
trip_plan_idstring | nulloptional
route_idstring | nulloptional
zone_idstring | nulloptional
bus_zone_idstring | nulloptional
directionstringrequiredhome_to_school school_to_home custom
started_atstring (date-time)required

Response

Returns { "data": TripWrite }.

FieldTypeConstraints
idstring (uuid)required
statusstringrequired
directionstringrequired
started_atstring | nullrequired
ended_atstring | nullrequired
expected_ridersintegerrequired
boarded_ridersintegerrequired
polylinestring | nullrequired
planned_polylinestring | nullrequired
ridersobject[]required
PATCH /v3/app/trip/{id} #
bearer driver_app beneficiary_app transport.trip_manage device credential

Path parameters

NameTypeNotes
idstring

Request body

FieldTypeConstraints
statusstringrequiredcompleted cancelled
ended_atstring (date-time)required

Response

Returns { "data": TripWrite }.

FieldTypeConstraints
idstring (uuid)required
statusstringrequired
directionstringrequired
started_atstring | nullrequired
ended_atstring | nullrequired
expected_ridersintegerrequired
boarded_ridersintegerrequired
polylinestring | nullrequired
planned_polylinestring | nullrequired
ridersobject[]required
POST /v3/app/trip/{id}/event #
bearer driver_app beneficiary_app transport.trip_manage device credential

alerts / SOS (terra-api §4.6).

Path parameters

NameTypeNotes
idstring

Request body

FieldTypeConstraints
kindstringrequiredgeofence_enter geofence_exit approach_alert arrival_alert overspeed route_deviation sos breakdown +2 more
severitystring | nulloptionalinfo warning critical
messagestring | nulloptionalmax length 2000
detailsstring | nulloptionalmax length 2000
occurred_atstring (date-time)required
client_event_idstring | nulloptional
student_idstring | nulloptional

Response

Returns { "data": TripEvent }.

FieldTypeConstraints
idstring (uuid)required
kindstringrequired
severitystring | nullrequired
messagestring | nullrequired
student_idstring | nullrequired
occurred_atstring (date-time)required
POST /v3/app/trip/{id}/position #
bearer driver_app beneficiary_app device credential gzip

batched, dedup by (trip_id, recorded_at) (terra-api §4.2).

Path parameters

NameTypeNotes
idstring

Headers

HeaderNotes
Content-EncodingoptionalOptional request-body compression; compressed cap 1 MB, decompressed cap 10 MB.

Request body

FieldTypeConstraints
positionsobject[]requiredmin items 1 · max items 500

Response

Returns { "data": PositionBatch }.

FieldTypeConstraints
acceptedintegerrequired
duplicatesintegerrequired
staleintegeroptional
next_position_secondsintegerrequired
POST /v3/app/trip/{id}/rider #
bearer driver_app beneficiary_app device credential gzip

non-tap attendance, per-item results (terra-api §4.3).

Path parameters

NameTypeNotes
idstring

Headers

HeaderNotes
Content-EncodingoptionalOptional request-body compression; compressed cap 1 MB, decompressed cap 10 MB.

Request body

FieldTypeConstraints
eventsRiderEventInput[]requiredmin items 1 · max items 500

Response

FieldTypeConstraints
resultsRiderResult[]required
countersRiderCountersrequired
POST /v3/app/trip/{id}/share-link #
bearer driver_app beneficiary_app parent_app device credential

expiring public tracking link (terra-api §4.10).

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": … }; this endpoint's payload is not yet pinned to a named shape.

GET /v3/geocode/reverse #
bearer driver_app beneficiary_app admin_web device credential

Query parameters

NameTypeConstraints
latnumberrequiredmin -90 · max 90
lngnumberrequiredmin -180 · max 180

Response

Returns { "data": … }; this endpoint's payload is not yet pinned to a named shape.

GET /v3/portal/fuel-price #
bearer admin_web device credential

Response

Returns { "data": FuelPrice }.

FieldTypeConstraints
dieselobject | nullrequired
diesel.price_kes_per_lnumberrequired
diesel.effective_datestringrequired
petrolobject | nullrequired
petrol.price_kes_per_lnumberrequired
petrol.effective_datestringrequired
stalebooleanrequired
GET /v3/portal/route #
bearer admin_web device credential

Query parameters

NameTypeConstraints
qstring | nulloptionalmax length 120
cursorstring | nulloptionalmax length 200
per_pageinteger | nulloptionalmin 1 · max 500

Response

Returns { "data": [WebRoute] } with meta.next_cursor for the next page.

FieldTypeConstraints
idstring (uuid)required
zone_idstring (uuid)required
namestringrequired
descriptionstring | nullrequired
statusstringrequired
est_durationinteger | nullrequired
est_distance_mnumber | nullrequired
rider_countintegerrequired
row_versionintegerrequired
POST /v3/portal/route #
bearer admin_web transport.plan device credential

Request body

FieldTypeConstraints
namestringrequiredmax length 120
descriptionstring | nulloptionalmax length 500
zone_idstring (uuid)required
est_duration_hoursinteger | nulloptionalmin 0 · max 23
est_duration_minutesinteger | nulloptionalmin 0 · max 59
est_distance_valuenumber | nulloptionalmin 0 · max 999
est_distance_unitstring | nulloptionalkm m
alert_settingsarray | nulloptional
alert_settings.high_delay_minutesinteger | nulloptionalmin 1 · max 480
alert_settings.medium_delay_minutesinteger | nulloptionalmin 1 · max 480

Response

Returns { "data": WebRouteDetail }.

FieldTypeConstraints
idstring (uuid)required
zone_idstring (uuid)required
namestringrequired
descriptionstring | nullrequired
statusstringrequired
est_durationinteger | nullrequired
est_distance_mnumber | nullrequired
row_versionintegerrequired
alert_settingsobjectrequired
alert_settings.high_delay_minutesintegerrequired
alert_settings.medium_delay_minutesintegerrequired
rider_countintegerrequired
stopsRouteStopRow[]required
trip_plansobject[]required
POST /v3/portal/route-assignment/apply #
bearer admin_web transport.plan device credential

Request body

FieldTypeConstraints
assignmentsobject[]requiredmin items 1

Response

Returns { "data": RouteAssignmentApply }.

FieldTypeConstraints
appliedstring (uuid)[]required
applied_routesobject[]required
failedobject[]required
DELETE /v3/portal/route-assignment/draft #
bearer admin_web transport.plan device credential

"Run another assignment" (2026-08-05 feedback, live-testing follow-up) — explicitly discards the tenant's current draft, since apply() no longer does this itself. See WebPlanningService::discardRouteAssignmentDraft().

Response

Returns { "data": RouteAssignmentDraftPatchResult }.

FieldTypeConstraints
okbooleanrequired
GET /v3/portal/route-assignment/draft #
bearer admin_web transport.plan device credential

The tenant's current Route Assignment draft, if any — see WebPlanningService::getRouteAssignmentDraft().

Response

Returns { "data": RouteAssignmentDraft }.

FieldTypeConstraints
foundbooleanrequired
computed_atstring | nullrequired
assignmentsobject[]required
skipped_vehiclesobject[]required
skipped_studentsobject[]required
savingsanyrequired
depotanyrequired
student_locationsobject[]required
applied_atstring | nullrequired
applied_resultanyrequired
PATCH /v3/portal/route-assignment/draft #
bearer admin_web transport.plan device credential

Renames a route within the tenant's current draft — see WebPlanningService::patchRouteAssignmentDraft().

Request body

FieldTypeConstraints
vehicle_zone_idstring (uuid)required
route_namestringrequiredmax length 120

Response

Returns { "data": RouteAssignmentDraftPatchResult }.

FieldTypeConstraints
okbooleanrequired
POST /v3/portal/route-assignment/measure #
bearer admin_web transport.plan device credential

Re-measures a drag-edited roster split ahead of apply() — see WebPlanningService::measureRouteAssignment().

Request body

FieldTypeConstraints
assignmentsobject[]requiredmin items 1

Response

Returns { "data": RouteAssignmentMeasure }.

FieldTypeConstraints
assignmentsobject[]required
savingsanyrequired
POST /v3/portal/route-assignment/suggest #
bearer admin_web transport.plan device credential

Request body

FieldTypeConstraints
vehicle_zone_idsstring (uuid)[]optional

Response

Returns { "data": RouteAssignmentSuggestion }.

FieldTypeConstraints
assignmentsobject[]required
skipped_vehiclesobject[]required
skipped_studentsobject[]required
savingsanyrequired
depotobjectrequired
depot.latnumberrequired
depot.lngnumberrequired
student_locationsobject[]required
computed_atstring (date-time)required
DELETE /v3/portal/route/{id} #
bearer admin_web transport.plan device credential

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": … }; this endpoint's payload is not yet pinned to a named shape.

GET /v3/portal/route/{id} #
bearer admin_web device credential

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": WebRouteDetail }.

FieldTypeConstraints
idstring (uuid)required
zone_idstring (uuid)required
namestringrequired
descriptionstring | nullrequired
statusstringrequired
est_durationinteger | nullrequired
est_distance_mnumber | nullrequired
row_versionintegerrequired
alert_settingsobjectrequired
alert_settings.high_delay_minutesintegerrequired
alert_settings.medium_delay_minutesintegerrequired
rider_countintegerrequired
stopsRouteStopRow[]required
trip_plansobject[]required
PATCH /v3/portal/route/{id} #
bearer admin_web transport.plan device credential

Path parameters

NameTypeNotes
idstring

Request body

FieldTypeConstraints
update_maskstring[]requiredmin items 1 · max items 9
expected_row_versionintegerrequiredmin 1
namestring | nulloptionalmax length 120
descriptionstring | nulloptionalmax length 500
statusstring | nulloptionalactive inactive archived
zone_idstring | nulloptional
est_duration_hoursinteger | nulloptionalmin 0 · max 23
est_duration_minutesinteger | nulloptionalmin 0 · max 59
est_distance_valuenumber | nulloptionalmin 0 · max 999
est_distance_unitstring | nulloptionalkm m
alert_settingsarray | nulloptional
alert_settings.high_delay_minutesinteger | nulloptionalmin 1 · max 480
alert_settings.medium_delay_minutesinteger | nulloptionalmin 1 · max 480

Response

Returns { "data": WebRouteDetail }.

FieldTypeConstraints
idstring (uuid)required
zone_idstring (uuid)required
namestringrequired
descriptionstring | nullrequired
statusstringrequired
est_durationinteger | nullrequired
est_distance_mnumber | nullrequired
row_versionintegerrequired
alert_settingsobjectrequired
alert_settings.high_delay_minutesintegerrequired
alert_settings.medium_delay_minutesintegerrequired
rider_countintegerrequired
stopsRouteStopRow[]required
trip_plansobject[]required
PUT /v3/portal/route/{id}/stops #
bearer admin_web transport.plan device credential

Path parameters

NameTypeNotes
idstring

Request body

FieldTypeConstraints
stopsobject[]requiredmax items 100

Response

Returns { "data": WebRouteDetail }.

FieldTypeConstraints
idstring (uuid)required
zone_idstring (uuid)required
namestringrequired
descriptionstring | nullrequired
statusstringrequired
est_durationinteger | nullrequired
est_distance_mnumber | nullrequired
row_versionintegerrequired
alert_settingsobjectrequired
alert_settings.high_delay_minutesintegerrequired
alert_settings.medium_delay_minutesintegerrequired
rider_countintegerrequired
stopsRouteStopRow[]required
trip_plansobject[]required
GET /v3/portal/trip #
bearer admin_web device credential

Query parameters

NameTypeConstraints
datestring | nulloptional
statusstring | nulloptionalscheduled active completed cancelled abandoned
route_idstring | nulloptional
cursorstring | nulloptionalmax length 200
per_pageinteger | nulloptionalmin 1 · max 500

Response

Returns { "data": [WebTrip] } with meta.next_cursor for the next page.

FieldTypeConstraints
idstring (uuid)required
statusstringrequired
directionstringrequired
started_atstring | nullrequired
ended_atstring | nullrequired
routeanyrequired
vehicleanyrequired
driverstringrequired
ridersobjectrequired
riders.expectedintegerrequired
riders.boardedintegerrequired
alerts_openintegerrequired
locationsobject[]required
GET /v3/portal/trip-plan #
bearer admin_web device credential

Query parameters

NameTypeConstraints
route_idstring | nulloptional
cursorstring | nulloptionalmax length 200
per_pageinteger | nulloptionalmin 1 · max 500

Response

Returns { "data": [WebTripPlan] } with meta.next_cursor for the next page.

FieldTypeConstraints
idstring (uuid)required
route_idstring | nullrequired
labelstring | nullrequired
directionstringrequired
schedulestringrequired
start_timestring | nullrequired
scheduled_onstring | nullrequired
recurring_daysarray | nullrequired
checkout_counts_as_rollcallbooleanrequired
statusstringrequired
row_versionintegerrequired
stops_stalebooleanrequired
POST /v3/portal/trip-plan #
bearer admin_web transport.plan device credential

Request body

FieldTypeConstraints
route_idstring (uuid)required
labelstringrequiredmax length 120
directionstring | nulloptionalhome_to_school school_to_home custom
schedulestring | nulloptionalonce recurring
start_timestring | nulloptional
scheduled_onstring | nulloptional
recurring_daysinteger[]optionalmax items 7
checkout_counts_as_rollcallboolean | nulloptional

Response

Returns { "data": WebTripPlan }.

FieldTypeConstraints
idstring (uuid)required
route_idstring | nullrequired
labelstring | nullrequired
directionstringrequired
schedulestringrequired
start_timestring | nullrequired
scheduled_onstring | nullrequired
recurring_daysarray | nullrequired
checkout_counts_as_rollcallbooleanrequired
statusstringrequired
row_versionintegerrequired
stops_stalebooleanrequired
DELETE /v3/portal/trip-plan/{id} #
bearer admin_web transport.plan device credential

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": … }; this endpoint's payload is not yet pinned to a named shape.

PATCH /v3/portal/trip-plan/{id} #
bearer admin_web transport.plan device credential

Path parameters

NameTypeNotes
idstring

Request body

FieldTypeConstraints
update_maskstring[]requiredmin items 1 · max items 9
expected_row_versionintegerrequiredmin 1
route_idstring | nulloptional
labelstring | nulloptionalmax length 120
directionstring | nulloptionalhome_to_school school_to_home custom
schedulestring | nulloptionalonce recurring
start_timestring | nulloptional
scheduled_onstring | nulloptional
recurring_daysinteger[]optionalmax items 7
checkout_counts_as_rollcallboolean | nulloptional
statusstring | nulloptionalactive suspended archived

Response

Returns { "data": WebTripPlan }.

FieldTypeConstraints
idstring (uuid)required
route_idstring | nullrequired
labelstring | nullrequired
directionstringrequired
schedulestringrequired
start_timestring | nullrequired
scheduled_onstring | nullrequired
recurring_daysarray | nullrequired
checkout_counts_as_rollcallbooleanrequired
statusstringrequired
row_versionintegerrequired
stops_stalebooleanrequired
GET /v3/portal/trip-plan/{id}/measure #
bearer admin_web transport.plan device credential

The currently-saved order's own distance/duration/road geometry — no reordering. Lets the manifest screen show a real baseline cost the moment it loads, not only after Auto-order runs.

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": MeasuredOrder }.

FieldTypeConstraints
distance_mnumberrequired
duration_snumberrequired
polylinestring | nullrequired
depotobjectrequired
depot.latnumberrequired
depot.lngnumberrequired
POST /v3/portal/trip-plan/{id}/measure-order #
bearer admin_web transport.plan device credential

the same distance/duration/road geometry measure-saved-order's GET sibling reports, but for a CALLER-SUPPLIED sequence rather than the currently-saved one. Never writes anything — what lets a manual drag/arrow reorder in progress show its own real cost/savings before Save.

Path parameters

NameTypeNotes
idstring

Request body

FieldTypeConstraints
student_idsstring (uuid)[]requiredmax items 200

Response

Returns { "data": MeasuredOrder }.

FieldTypeConstraints
distance_mnumberrequired
duration_snumberrequired
polylinestring | nullrequired
depotobjectrequired
depot.latnumberrequired
depot.lngnumberrequired
GET /v3/portal/trip-plan/{id}/rider #
bearer admin_web device credential

the ordered manifest with navigation stops.

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": [PlanRider] } with meta.next_cursor for the next page.

FieldTypeConstraints
student_idstring (uuid)required
namestringrequired
gradestring | nullrequired
positionintegerrequired
pickup_stop_idstring | nullrequired
dropoff_stop_idstring | nullrequired
latnumber | nullrequired
lngnumber | nullrequired
PUT /v3/portal/trip-plan/{id}/rider-order #
bearer admin_web transport.plan device credential

Path parameters

NameTypeNotes
idstring

Request body

FieldTypeConstraints
student_idsstring (uuid)[]requiredmax items 200
stopsobject[]optionalmax items 200
from_optimizerboolean | nulloptional

Response

Returns { "data": [PlanRider] } with meta.next_cursor for the next page.

FieldTypeConstraints
student_idstring (uuid)required
namestringrequired
gradestring | nullrequired
positionintegerrequired
pickup_stop_idstring | nullrequired
dropoff_stop_idstring | nullrequired
latnumber | nullrequired
lngnumber | nullrequired
GET /v3/portal/trip-plan/{id}/suggest-order #
bearer admin_web transport.plan device credential

the ML platform's suggested visiting sequence for this plan's current riders. Never writes anything; the caller pre-fills the editable manual order with this and only setRiderOrder() (a separate, explicit save) persists it — "editable defaults, not locked" (Faith, 2026-07-21).

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": SuggestedOrder }.

FieldTypeConstraints
ridersPlanRider[]required
optimal_distance_mnumberrequired
optimal_duration_snumberrequired
optimal_polylinestring | nullrequired
saved_distance_mnumber | nullrequired
saved_duration_snumber | nullrequired
distance_saved_mnumber | nullrequired
duration_saved_snumber | nullrequired
depotobjectrequired
depot.latnumberrequired
depot.lngnumberrequired
GET /v3/portal/trip/{id} #
bearer admin_web device credential

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": WebTripDetail }.

FieldTypeConstraints
idstring (uuid)required
statusstringrequired
directionstringrequired
started_atstring | nullrequired
ended_atstring | nullrequired
routeanyrequired
driverstring | nullrequired
ridersobject[]required
eventsobject[]required
checkpointsRouteStopRow[]required
polylinestring | nullrequired
planned_polylinestring | nullrequired
POST /v3/portal/trip/{id}/end #
bearer admin_web transport.trip_manage device credential

The dispatcher's end (docs/terra-api.md §11.6, absorbs the v2 `trips/update/{id}` PUT): a school admin closes a stuck or forgotten trip from the web board. Same terminal transition as the driver's PATCH — driver-ownership waived, tenant fence intact, idempotent on an already-ended trip.

Path parameters

NameTypeNotes
idstring

Request body

FieldTypeConstraints
statusstringrequiredcompleted cancelled
ended_atstring (date-time)required

Response

Returns { "data": TripEndResult }.

FieldTypeConstraints
idstring (uuid)required
statusstringrequired
ended_atstring (date-time)required
GET /v3/portal/trip/{id}/replay #
bearer admin_web device credential

The trip-history replay artifact — v2's trip file {polyline, timestamps, metadata}, now an authenticated read built lazily from trip_positions (docs/terra-api.md §11.6). Only a finished trip replays; its path is immutable, so browsers may cache hard.

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": TripReplay }.

FieldTypeConstraints
polylinestringrequired
timestampsstring | null[]required
metadataarrayrequired
points_countintegerrequired
snappedbooleanrequired
generated_atstring (date-time)required
GET /v3/portal/trip/{id}/route-comparison #
bearer admin_web device credential

"Did the driver follow the plan, what could've been saved" — Trip History's optimizer-comparison ask, same completed-trip gate as replay(). Best-effort: a comparison the ML platform can't produce (down, or not enough usable stop/GPS data on this trip) responds 404 rather than 500 — matching ml.md's fallback posture at the boundary the web board actually sees.

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": RouteComparison }.

FieldTypeConstraints
optimal_orderstring[]required
stop_locationsRouteComparisonStop[]required
optimal_distance_mnumber | nullrequired
optimal_duration_snumber | nullrequired
optimal_polylinestring | nullrequired
actual_distance_mnumberrequired
actual_duration_snumberrequired
distance_saved_mnumberrequired
duration_saved_snumberrequired
GET /v3/portal/trip/{id}/route-segments #
bearer admin_web device credential

Segment-by-segment: did the driver take a good ROAD between each real, consecutive stop — routeComparison()'s sibling, but Route Path's question ("was the driver efficient here") rather than Stop Ordering's ("was this the best order"). Same completed-trip gate and best-effort 404-not-500 posture as routeComparison().

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": RouteSegments }.

FieldTypeConstraints
segmentsobject[]required
GET /v3/track/{code} #
public 30,1

Path parameters

NameTypeNotes
codestring

Response

Returns { "data": … }; this endpoint's payload is not yet pinned to a named shape.

GET /v3/trip/live/{id} #
bearer driver_app beneficiary_app parent_app admin_web device credential

the Redis-backed snapshot (terra-api §4.5); a tenant-scoped read (driver, dashboard, and the parent app's §6.4 all read the same shape). The SSE stream lands with the realtime slice.

Path parameters

NameTypeNotes
idstring

Response

Returns { "data": TripLive }.

FieldTypeConstraints
trip_idstring (uuid)required
statusstringrequired
boarded_ridersinteger | nulloptional
expected_ridersinteger | nulloptional
last_positionanyoptional
eta_schoolstring | nulloptional
planned_polylinestring | nulloptional
stop_etasobject[]optional
next_poll_secondsintegeroptional
POST /v3/trip/{id}/handover #
bearer driver_app beneficiary_app admin_web transport.trip_manage device credential

breakdown reassignment (terra-api §4.12).

One capability, two principals (lego L1b): a handset hands over only its OWN trip, while the console — the dispatcher taking the breakdown phone call (uber UB-21c) — has the driver requirement waived by its surface. The fence is identical either way; only the ownership rule is per-principal.

Path parameters

NameTypeNotes
idstring

Request body

FieldTypeConstraints
target_trip_idstring (uuid)required
occurred_atstring (date-time)required

Response

Returns { "data": TripHandover }.

FieldTypeConstraints
moved_ridersintegerrequired
target_trip_idstring (uuid)required
dead_trip_statusstringrequired
POST /v3/webhook/geofence #
public 300,1

Response

Returns { "data": … }; this endpoint's payload is not yet pinned to a named shape.