V2 (Mobile Sync)
Versioned /api/v2 endpoints for the driver mobile app: slim, idempotent, offline-first and delta-syncable variants of production endpoints (auth/bootstrap, trips, checkpoints, alerts, GPS, roster, band scan, batch outbox drain, and delta-sync history feeds). Kept separate from v1 so production routes stay untouched.
POST Login (Staff) {{url}}/v2/auth/login ▸
Slim staff login for the driver app. The server resolves the Staff role itself (no separate roles call) and returns the staff profile, their assigned zones/locations, the saved default zone/location, and a short-lived temp_token. Public (no bearer). Complete the flow with Session.
Body
- email (
string, required) - password (
string, required) - client (
string, optional, defaultapp):app|web. - remember_me (
boolean, optional).
{
"email": "yamofaith@gmail.com", // User's email address
"password": "Terra@2025", // User's password
"client": "app" // Client type: app or web
}
POST Session (Select Zone/Location + Bootstrap) {{url}}/v2/auth/session ▸
Second step of the staff login: exchange the temptoken + chosen zone/location for real session tokens, with the bootstrap payload folded in (no separate bootstrap call), and persist the chosen zone/location as the default. Public (the temptoken is the credential).
Body
- temp_token (
string, required): from Login. - waaszoneuuid (
string, required): the chosen zone/location. - remember_me (
boolean, optional).
Response
Session tokens plus the folded-in bootstrap (data): accesstoken, refreshtoken, tokentype (bearer), expiresin (minutes), and me / zone / tags / routes / trip_planners.
{
"temp_token": "REPLACE_WITH_TEMP_TOKEN", // Short-lived token returned by the login step
"waas_zone_uuid": "825e7402-a072-4926-9bfb-27280c2be5eb", // Zone/location UUID (from the WaaS partner system)
"remember_me": false // Keep the session logged in longer
}
POST Switch Zone/Location {{url}}/v2/auth/switch-zone ▸
Authenticated in-app zone/location switch: re-issues zone/location-scoped tokens plus the new zone/location's bootstrap and updates the default, without re-login.
Body
- waaszoneuuid (
string, required): the zone/location to switch to. - remember_me (
boolean, optional).
Requires a bearer token (auth:api).
Response
New zone-scoped tokens plus the new zone's bootstrap (data).
{
"waas_zone_uuid": "825e7402-a072-4926-9bfb-27280c2be5eb", // Zone/location UUID (from the WaaS partner system)
"remember_me": false // Keep the session logged in longer
}
GET Bootstrap {{url}}/v2/bootstrap ▸
All UI-critical config in one call — me / role / zone/location / tags / routes / trip_planners. Supports ETag / If-None-Match → 304 Not Modified so the app skips re-downloading unchanged config.
Headers
- If-None-Match (optional): the ETag from a previous response; a match returns
304.
Requires a bearer token (auth:api).
GET List Students (v2) {{url}}/v2/dependants?tags=<tag-uuid>&trip_planner_id=da2175e7-cc4d-4c25-a8e8-4ad81300a9e1&per_page=50 ▸
Mirrors GET /api/students but every student carries a non-null boarding status and (when assigned) its trip_planners. Cursor-paginated, delta-syncable.
Query Parameters
- tags (comma-separated tag uuids): required for non-privileged callers (driver/minder); admins/parents exempt.
- tripplannerid (
stringUUID, optional): restrict to students assigned to this planner (viadependantrouteorders). - trip_id (
stringUUID, optional): scope boarding status to a specific trip. - perpage (
integer, optional, default 15, max 100), cursor (opaque), updatedsince (delta sync). - search, grade, isactive, parentid, phone_number (optional filters).
Response
Cursor page of slim students; meta.nextcursor + meta.maxupdated_at drive the next sync.
Requires a bearer token (auth:api).
GET Zones/Locations (v2) {{url}}/v2/zones?per_page=50 ▸
The utility/zones/locations payload (TerraGo-sourced) as a cursor + updated_since delta feed for the app.
Query Parameters
- cursor (opaque, optional), updatedsince (optional), perpage (
integer, optional).
Requires a bearer token (auth:api).
Response
A cursor page of zones (data) with meta.nextcursor and meta.maxupdated_at for delta sync.
POST Start Trip (Offline-startable) {{url}}/v2/trip ▸
Offline-startable trip creation. Accepts a client-generated uuid as the canonical trips.uuid; idempotent upsert-by-uuid (a replay is a no-op), so a driver can start a trip with zero round-trips and run the whole shift offline. v1 trips/add is untouched.
Body
- uuid (
stringUUID, required): client-generated trip id. - status (
string, required): aTrip::STATUSkey (e.g.ACTIVE). - start_time (
date, required). - waaszoneuuid / waaszonename (optional).
- routeid (
stringUUID, optional), tripplanner_id (stringUUID, optional). - trip_description (
string, optional), direction (optional):H2S|S2H.
Requires a bearer token (auth:api).
Response
The created trip (data), keyed by the client uuid. Replaying the same uuid returns the existing trip (no duplicate).
{
"uuid": "d5d46e78-8792-4cee-aec6-a43dbdbc4716", // Client-generated UUID (also the idempotency key)
"status": "ACTIVE", // Status value — one of the allowed statuses for this resource
"start_time": "2026-07-09 06:30:00", // Trip start time (timestamp)
"waas_zone_uuid": "825e7402-a072-4926-9bfb-27280c2be5eb", // Zone/location UUID (from the WaaS partner system)
"waas_zone_name": "Y Zone", // Zone/location display name
"route_id": "643ab899-5b74-4dc1-b3a0-5b512dc11336", // Route UUID
"trip_planner_id": "da2175e7-cc4d-4c25-a8e8-4ad81300a9e1", // Trip planner UUID (which trip this belongs to)
"direction": "H2S" // H2S (Home→School) or S2H (School→Home)
}
POST Complete Trip {{url}}/v2/trip/d5d46e78-8792-4cee-aec6-a43dbdbc4716/complete ▸
Complete a trip in one idempotent call, folding the device-metrics summary in (complete + metrics → one request). v1 trips/update is untouched.
Path Parameters
- uuid (
stringUUID, required): the trip uuid.
Body
- end_time (
date, required), status (Trip::STATUSkey, required). - trip_description (
string, optional). - metrics (
object, optional):batterystarttrip,batteryendtrip(0–100),signalavg(-120–0),disconnectcount,offlinedurationseconds,gpspointscount,scaneventscount,expectedstudents,detectedstudents,lte(bool),network_type,firmware.
Requires a bearer token (auth:api).
Response
The completed trip (data) with its folded-in device-metrics summary.
{
"end_time": "2026-07-09 07:52:00", // Trip end time (timestamp)
"status": "COMPLETED", // Status value — one of the allowed statuses for this resource
"metrics": { // Device metrics summary for the trip
"battery_start_trip": 95, // Device battery % at trip start (0–100)
"battery_end_trip": 80, // Device battery % at trip end (0–100)
"gps_points_count": 240, // Number of GPS points recorded
"expected_students": 12, // Students expected on the trip
"detected_students": 11 // Students actually detected / scanned
}
}
POST Add Trip GPS Locations (Batch, v2) {{url}}/v2/trip/locations ▸
Idempotent bulk GPS breadcrumbs — the app buffers points offline and flushes them in one batch; a replay is a no-op via the dedup unique index. v1 trips/trip-locations is untouched.
Body
- trip_id (
stringUUID, required). - points (
array, required, 1–500): each{ latitude (-90..90), longitude (-180..180), recorded_at (date), metadata (object, optional) }.
Requires a bearer token (auth:api).
Response
Confirmation of how many points were stored (data); already-seen points are ignored (idempotent).
{
"trip_id": "d5d46e78-8792-4cee-aec6-a43dbdbc4716", // Trip UUID
"points": [ // List of GPS breadcrumbs (latitude, longitude, recorded_at)
{ "latitude": -1.2921, "longitude": 36.8219, "recorded_at": "2026-07-09 06:31:00" },
{ "latitude": -1.2935, "longitude": 36.8250, "recorded_at": "2026-07-09 06:32:00" }
]
}
POST Record Trip Checkpoint (v2) {{url}}/v2/trip/checkpoint ▸
Record a pickup/dropoff checkpoint. Idempotent upsert on (student, trip, type) so a replayed offline checkpoint refreshes the same row. Used online and on offline drain. v1 trip-check-points is untouched.
Body
- dependantid (
stringUUID, required), tripid (stringUUID, required). - latitude (-90..90, required), longitude (-180..180, required).
- type (required): a
CheckpointType(e.g.PICKUP/DROPOFF). - occurred_at (
date, optional).
Requires a bearer token (auth:api).
Response
The recorded checkpoint (data); a replayed offline checkpoint refreshes the same row.
{
"dependant_id": "184c32e2-878d-4e89-99c3-91dda70c03e9", // Student UUID
"trip_id": "d5d46e78-8792-4cee-aec6-a43dbdbc4716", // Trip UUID
"latitude": -1.2921, // GPS latitude (-90 to 90)
"longitude": 36.8219, // GPS longitude (-180 to 180)
"type": "PICKUP", // Type / category — one of the allowed values
"occurred_at": "2026-07-09 06:35:00" // When the checkpoint happened (timestamp)
}
POST Raise Trip Alert (v2) {{url}}/v2/trip/d5d46e78-8792-4cee-aec6-a43dbdbc4716/alert ▸
Raise a trip alert. Idempotent on the client raise uuid (stored as the shared event_hash), so an offline alert that re-drains never duplicates. Used online and on offline drain. v1 {trip}/raise-alert is untouched.
Path Parameters
- uuid (
stringUUID, required): the trip uuid.
Body
- uuid (
stringUUID, required): idempotency key for the raise. - eventtype (required): an
AlertAndEvent::EVENTTYPEkey. - severity (optional): an
AlertAndEvent::SEVERITYkey. - message (
string, required, max 500), event_details (string, optional, max 2000). - recordedat (
date, optional), dependantids (arrayof UUID, optional).
Requires a bearer token (auth:api).
Response
The raised alert (data); a replay with the same uuid is not duplicated.
{
"uuid": "7b1f0c9a-1111-4a22-9c33-abcdef012345", // Client-generated UUID (also the idempotency key)
"event_type": "SOS", // Alert type — one of the allowed event types
"severity": "HIGH", // Alert severity — one of the allowed levels
"message": "Driver raised an SOS alert", // Message text sent to the recipient
"recorded_at": "2026-07-09 06:40:00", // When the event actually happened (timestamp)
"dependant_ids": ["184c32e2-878d-4e89-99c3-91dda70c03e9"] // List of student UUIDs
}
GET Identify Band by Serial (v2) {{url}}/v2/band/23059643U9876345203 ▸
Identify a scanned band by serial number: returns who it belongs to plus the band's route tag(s), as a slim non-null object. Online enrichment for the scan flow; mirrors v1 utility/band/bag tag-info/{serial} on a short v2 route and surfaces the tags.
Path Parameters
- serial (
string, required): the band serial number.
Requires a bearer token (auth:api).
POST Batch (Outbox Drain) {{url}}/v2/batch ▸
Drain many queued TerraGo-owned outbox actions in ONE request (idempotent, per-item applied | duplicate | rejected). Collapses an end-of-shift drain from N requests to ~2.
Body
- items (
array, required, 1–200): each{ type (string), idempotencykey (string), payload (object), patharg (string, optional) }.
Requires a bearer token (auth:api).
Response
Per-item results (data): each queued action comes back applied, duplicate, or rejected.
{
"items": [ // List of queued actions to apply in one batch
{
"type": "trip.checkpoint", // Type / category — one of the allowed values
"idempotency_key": "ck-0001", // Unique key so a replayed item isn't applied twice
"payload": { // The action's data (same fields as its own endpoint)
"dependant_id": "184c32e2-878d-4e89-99c3-91dda70c03e9", // Student UUID
"trip_id": "d5d46e78-8792-4cee-aec6-a43dbdbc4716", // Trip UUID
"latitude": -1.2921, // GPS latitude (-90 to 90)
"longitude": 36.8219, // GPS longitude (-180 to 180)
"type": "PICKUP" // Type / category — one of the allowed values
}
}
]
}
GET Trip History (Delta Sync) {{url}}/v2/trips/history?per_page=50 ▸
The signed-in driver's own trip history as a delta-syncable cursor feed (keyset on trips.id + updatedsince WHERE on updatedat; omit updated_since for a full pull). Slim, sync-oriented rows. v1 trips is untouched.
Query Parameters
- cursor (opaque, optional), updatedsince (optional), perpage (
integer, optional).
Requires a bearer token (auth:api).
Response
A cursor page of the driver's trips (data) with meta.nextcursor and meta.maxupdated_at for delta sync.
GET Verifications History (Delta Sync) {{url}}/v2/verifications/history?per_page=50 ▸
The driver app's gate-verification history, served from TerraGo's local verifications projection (no per-sync the registry hop), enriched with the verifying guardian (from AuthorizationOtpHistory) + student image. Delta-syncable keyset-cursor feed (same contract as trip history). Replaces the app's direct the registry beneficiary-app/verifications/history call.
Query Parameters
- cursor (opaque, optional), updatedsince (optional), perpage (
integer, optional).
Requires a bearer token (auth:api).
Response
A cursor page of gate-verification records (data) with a meta cursor — each enriched with the verifying guardian and the student's image.
POST Tile38 Geofence Webhook {{url}}/v2/geofence?t=<webhook-secret> ▸
Tile38 roaming-geofence webhook — fires when a driver crosses a student's home. Public: Tile38 sends no auth header, so the caller is verified by the shared secret carried as the t query param. Each detection is fanned out to SendDropoffNotification (dedup + parent delivery).
Query Parameters
- t (
string, required): the shared webhook secret.
Body
The Tile38 hook detection payload (object/point + fields). Not called by clients — configured on the Tile38 server.
Response
A 200 acknowledgement; each detection is queued for parent notification. Returns 401/403 if the t secret is wrong.
POST Record Roll Call (v2) {{url}}/v2/rollcall/add ▸
Mirrors POST /api/rollcall/add but idempotent and always 200 (no "already recorded" rejection), so an offline re-drain never fails.
Body
- status (required): a
RollCall::STATUSkey (e.g.PRESENT). - waaszoneuuid (
stringUUID, required), waaszonename (string, required). - dependant_id (
stringUUID, required). - notes (
string, optional).
Requires a bearer token (auth:api).
Response
The recorded roll-call (data); always 200 (idempotent), even on a replay.
{
"status": "PRESENT", // Status value — one of the allowed statuses for this resource
"waas_zone_uuid": "825e7402-a072-4926-9bfb-27280c2be5eb", // Zone/location UUID (from the WaaS partner system)
"waas_zone_name": "Y Zone", // Zone/location display name
"dependant_id": "184c32e2-878d-4e89-99c3-91dda70c03e9", // Student UUID
"notes": null // Optional free-text notes
}