TerraGo API

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.

17 endpoint(s)
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, default app): app | web.
  • remember_me (boolean, optional).
Auth
Bearer token (inherited)
Request Body
{
    "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.

Auth
Bearer token (inherited)
Request Body
{
    "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).

Auth
Bearer token
Request Body
{
    "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-Match304 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).

Auth
Bearer token
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 (string UUID, optional): restrict to students assigned to this planner (via dependantrouteorders).
  • trip_id (string UUID, 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).

Auth
Bearer token
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.

Auth
Bearer token
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 (string UUID, required): client-generated trip id.
  • status (string, required): a Trip::STATUS key (e.g. ACTIVE).
  • start_time (date, required).
  • waaszoneuuid / waaszonename (optional).
  • routeid (string UUID, optional), tripplanner_id (string UUID, 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).

Auth
Bearer token
Request Body
{
    "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 (string UUID, required): the trip uuid.

Body

  • end_time (date, required), status (Trip::STATUS key, 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.

Auth
Bearer token
Request Body
{
    "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 (string UUID, 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).

Auth
Bearer token
Request Body
{
    "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 (string UUID, required), tripid (string UUID, 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.

Auth
Bearer token
Request Body
{
    "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 (string UUID, required): the trip uuid.

Body

  • uuid (string UUID, required): idempotency key for the raise.
  • eventtype (required): an AlertAndEvent::EVENTTYPE key.
  • severity (optional): an AlertAndEvent::SEVERITY key.
  • message (string, required, max 500), event_details (string, optional, max 2000).
  • recordedat (date, optional), dependantids (array of UUID, optional).

Requires a bearer token (auth:api).

Response

The raised alert (data); a replay with the same uuid is not duplicated.

Auth
Bearer token
Request Body
{
    "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).

Auth
Bearer token
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.

Auth
Bearer token
Request Body
{
    "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.

Auth
Bearer token
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.

Auth
Bearer token
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.

Auth
Bearer token (inherited)
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::STATUS key (e.g. PRESENT).
  • waaszoneuuid (string UUID, required), waaszonename (string, required).
  • dependant_id (string UUID, required).
  • notes (string, optional).

Requires a bearer token (auth:api).

Response

The recorded roll-call (data); always 200 (idempotent), even on a replay.

Auth
Bearer token
Request Body
{
    "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
}