Conventions
What is true of every endpoint, so the module pages do not repeat it.
Base URLs
| Environment | Base URL |
|---|---|
| Production | https://api.goterrago.com |
| Staging | https://terra-stg.goterrago.com |
Paths on this site are absolute and include their version segment — /v3/….
Authentication
Every endpoint marked bearer expects a short-lived access JWT:
Authorization: Bearer <access_token>
Tokens are minted by POST /v3/auth/login and refreshed by
POST /v3/auth/refresh. They are audience-bound: a token minted for one
surface is refused on another, and each endpoint below lists the audiences it admits.
Audiences
The audience says which client a token was issued to. It is not a role — it is the app.
| Audience | Client |
|---|---|
driver_app | TerraGo driver handset |
beneficiary_app | WaaS operator / beneficiary app |
parent_app | Parent app |
pass_app | TerraPass venue handset |
pass_web | TerraPass operations console |
pass_fan | TerraPass fan portal (ticket holders) |
admin_web | Back-office dashboards |
Capabilities
Where an endpoint shows capability, the token's holder
must also carry that grant. Capabilities are resolved per user from their tenant role, or
from the bundle their staff_kind implies, and they are enforced on the route —
a client that hides a button is a courtesy, not a control. A caller without the grant gets
403 missing_capability.
Device fingerprint
Endpoints marked device require the caller to identify the physical unit:
X-Device-Fingerprint: <stable client-minted id, 16+ chars>
It must match the device the token was issued to — otherwise 401 device_mismatch,
and a device an operator has blocked gets 403 device_blocked immediately.
Response envelopes
Every success is wrapped. A single record:
{ "data": { … } }
A collection, with its cursor:
{ "data": [ … ], "meta": { "next_cursor": "…", "has_more": true } }
Pass ?cursor= to continue. Cursors are opaque — do not parse them, and do not
assume a page size.
Idempotency
Endpoints marked idempotency require a key:
Idempotency-Key: <uuidv7>
Mint it once, at the moment the action is taken, and send the same key on every retry. This is what makes a payment taken on a handset with no signal exactly one payment however many times the queue drains it.
Money and time
Money is always { "amount_minor": 12000, "currency": "KES" } — an integer in
the currency's minor unit, never a float. Timestamps are ISO-8601 UTC with a
Z. Identifiers are UUIDv7 strings.
Rate limits
Each endpoint shows the named limiter it runs under. Exceeding it answers
429 with a Retry-After header. The named limiters are shared —
credential is per authenticated caller, login is per address and
IP, and tap is sized for a door moving a person a second.
Compressed uploads
Batch endpoints accept Content-Encoding: gzip. The compressed body is capped
at 1 MB and the decompressed body at 10 MB.