# Cabgo API v1 — single-file reference

> Canonical issuer: https://www.cabgo.app
> OpenAPI: /openapi.json · Interactive: /api-docs · Auth guide: /docs/api

## At a glance

Cabgo is a multi-tenant transport / delivery platform. The API is OAuth 2.0 / OIDC. Every access token carries a `tenant_id` claim that scopes every request — a token for tenant A cannot read tenant B's data, ever. Tokens have an `aud` (audience) claim that picks which actor type they represent:

- `dashboard` — Cabgo operator (B2B admin). Scopes start with `tenant.`.
- `rider` — End-user / customer. Scopes start with `rider.`.
- `driver` — Driver. Scopes start with `driver.`.

Permission / feature gating happens at every request, not just at token issuance — see _Defense in depth_ below.

## Authentication quickstart

### 1. Register an OAuth client

```bash
curl -X POST https://www.cabgo.app/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My integration",
    "redirect_uris": ["http://127.0.0.1:8080/callback"],
    "token_endpoint_auth_method": "none",
    "audiences": ["dashboard"],
    "scope": "openid tenant.trips:read tenant.drivers:read"
  }'
```

Response includes `client_id`, optional `client_secret`, and a `registration_access_token` for self-service updates (RFC 7592).

### 2. Open the consent page (authorization_code + PKCE)

```
https://www.cabgo.app/oauth/authorize?response_type=code&client_id={id}&redirect_uri=...&scope=openid+tenant.trips%3Aread&state={state}&code_challenge={challenge}&code_challenge_method=S256
```

- For rider / driver audiences, add `tenant_hint={tenantSlug}` so the consent screen renders with the tenant's branding (it redirects to the tenant's custom domain when configured).
- Public clients (mobile / CLI) **must** use PKCE S256.

### 3. Exchange the code for tokens

```bash
curl -X POST https://www.cabgo.app/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=...&redirect_uri=...&client_id=...&code_verifier=..."
```

Returns:

```json
{"access_token":"eyJ...","refresh_token":"...","token_type":"Bearer","expires_in":3600,"scope":"openid tenant.trips:read"}
```

### 4. Call the API

```bash
curl https://www.cabgo.app/api/v1/me -H "Authorization: Bearer $ACCESS_TOKEN"
```

### 5. Refresh

Access tokens live 1h. Refresh tokens live 90 days and rotate on every use. **Reuse of an already-rotated refresh token revokes the entire chain** (rider + driver tokens issued from the same flow) — never share a refresh token between processes.

## Defense in depth — what gets re-checked at every request

1. JWT signature against the JWKS at `/.well-known/jwks.json`.
2. Audit row in `OAuthAccessToken` (any revocation kills the token even if the JWT is cryptographically valid).
3. Endpoint audience requirement.
4. Endpoint scope requirement.
5. **Operator-only:** `MembershipPermission` still grants the required `Permission`. Removing `VIEW_TRIPS` from a user invalidates their `tenant.trips:read` tokens immediately.
6. **End-user only:** the tenant feature flag the scope depends on is still on. Disabling `deliveryEnabled` instantly invalidates riders' `rider.orders:*` tokens.

## Scope catalog

### OIDC compat (any audience)

| Scope | Description | Requires |
| --- | --- | --- |
| `openid` | OIDC authentication | — |
| `profile` | Read profile claims | — |
| `email` | Read email claim | — |

### Operator (audience=dashboard)

| Scope | Description | Requires |
| --- | --- | --- |
| `tenant.trips:read` | List and read trips for the tenant | requires Permission `VIEW_TRIPS` |
| `tenant.trips:write` | Create and update trips for the tenant | requires Permission `CREATE_TRIPS` |
| `tenant.trips:delete` | Delete trips for the tenant | requires Permission `DELETE_TRIPS` |
| `tenant.drivers:read` | List and read driver profiles | requires Permission `VIEW_DRIVERS` |
| `tenant.drivers:write` | Create and update drivers | requires Permission `CREATE_DRIVERS` |
| `tenant.customers:read` | List and read customer profiles | requires Permission `VIEW_CUSTOMERS` |
| `tenant.customers:write` | Create and update customers | requires Permission `CREATE_CUSTOMERS` |
| `tenant.settings:read` | Read tenant settings (pricing, zones, services) | requires Permission `VIEW_SETTINGS` |
| `tenant.settings:write` | Update tenant settings | requires Permission `EDIT_SETTINGS` |
| `tenant.reports:read` | Read reports and analytics | requires Permission `VIEW_REPORTS` |
| `tenant.reports:export` | Export reports | requires Permission `EXPORT_REPORTS` |
| `tenant.businesses:read` | List businesses (delivery module) | requires Permission `VIEW_BUSINESSES` |
| `tenant.businesses:write` | Create and update businesses | requires Permission `MANAGE_BUSINESSES` |
| `tenant.delivery:read` | List food orders | requires Permission `VIEW_FOOD_ORDERS` |
| `tenant.delivery:write` | Update food orders | requires Permission `MANAGE_FOOD_ORDERS` |
| `tenant.wallets:read` | Read driver / customer wallet balances | requires Permission `VIEW_WALLETS` |
| `tenant.wallets:write` | Adjust wallets and approve withdrawals | requires Permission `MANAGE_WALLETS` |
| `tenant.branding:write` | Update branding (logos, colors, copy) | requires Permission `EDIT_BRANDING` |

### Rider (audience=rider)

| Scope | Description | Requires |
| --- | --- | --- |
| `rider.profile:read` | Read your profile | — |
| `rider.profile:write` | Update your profile | — |
| `rider.trips:read` | Read your trips | — |
| `rider.trips:create` | Request a ride | requires `taxiEnabled` flag |
| `rider.trips:cancel` | Cancel a trip you requested | — |
| `rider.orders:read` | Read your delivery orders | requires `deliveryEnabled` flag |
| `rider.orders:create` | Place a delivery order | requires `deliveryEnabled` flag |
| `rider.addresses:write` | Manage your saved addresses | — |
| `rider.payments:read` | List your payment methods | — |
| `rider.wallet:read` | Read your wallet balance | — |

### Driver (audience=driver)

| Scope | Description | Requires |
| --- | --- | --- |
| `driver.profile:read` | Read your driver profile | — |
| `driver.status:write` | Go online or offline | — |
| `driver.trips:read` | Read your assigned trips | — |
| `driver.trips:accept` | Accept incoming ride offers | — |
| `driver.trips:complete` | Mark trips as arrived / started / completed | — |
| `driver.wallet:read` | Read your earnings and wallet | — |
| `driver.documents:read` | Read the status of your uploaded documents | — |

## Endpoints

### Rider

#### `GET /api/v1/me` — Read your profile

Polymorphic profile endpoint. Returns the operator's User + active Membership, the rider's Customer record, or the driver's Driver record — chosen automatically based on the token's `aud` claim.

- **OperationId:** `cabgo_me`
- **Scopes:** `rider.profile:read`, `driver.profile:read`
- **Audience:** `dashboard` / `rider` / `driver`

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/me
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/me",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

**Response example:**

```json
{
  "actor": "customer",
  "id": "cust_2P...",
  "firstName": "María",
  "lastName": "García",
  "email": "maria@example.com",
  "phone": "+525500001234",
  "totalTrips": 47,
  "rating": 4.85
}
```

---

#### `GET /api/v1/me/trips` — List your trips

Rider: trips you requested. Driver: trips assigned to you. Operator audience returns 400 — use `/api/v1/trips` instead.

- **OperationId:** `cabgo_my_trips`
- **Scopes:** `rider.trips:read`, `driver.trips:read`
- **Audience:** `rider` / `driver`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `limit` | query | integer | — |

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/me/trips
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me/trips", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/me/trips",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

#### `POST /api/v1/me/trips` — Request a ride

Creates a PENDING trip for the authenticated rider. Requires `tenant.taxiEnabled = true`. `paymentType` defaults to `CASH` and `serviceTypeId` is optional — when omitted the tenant's default service is used.

- **OperationId:** `cabgo_request_ride`
- **Scopes:** `rider.trips:create`
- **Tenant feature:** `taxiEnabled` must be `true`
- **Audience:** `rider`

**Request body example:**

```json
{
  "originAddress": "Av. Reforma 222, Cuauhtémoc, CDMX",
  "originLat": 19.4326,
  "originLng": -99.1332,
  "destAddress": "Aeropuerto CDMX T1",
  "destLat": 19.4361,
  "destLng": -99.0727,
  "paymentType": "CARD"
}
```

**curl:**

```bash
curl -X POST -H "Authorization: Bearer $CABGO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"originAddress":"Reforma 222","originLat":19.4326,"originLng":-99.1332,"paymentType":"CARD"}' \
  https://www.cabgo.app/api/v1/me/trips
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me/trips", {
  method: "POST",
  headers: { "Authorization": `Bearer ${CABGO_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    originAddress: "Reforma 222",
    originLat: 19.4326,
    originLng: -99.1332,
    paymentType: "CARD",
  }),
});
const trip = await res.json();
```

**Python:**

```python
import requests
res = requests.post(
    "https://www.cabgo.app/api/v1/me/trips",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
    json={"originAddress":"Reforma 222","originLat":19.4326,"originLng":-99.1332,"paymentType":"CARD"},
)
data = res.json()
```

**Response example:**

```json
{
  "id": "tr_01HK9F2ZTYP3JK4QXX7BD2N3V8",
  "tripCode": "T-A1B2C3",
  "status": "PENDING",
  "driverId": "drv_8H...",
  "customerId": "cust_2P...",
  "originAddress": "Av. Reforma 222, Cuauhtémoc, CDMX",
  "destAddress": "Aeropuerto CDMX T1",
  "estimatedFare": 245.5,
  "finalFare": 250,
  "currency": "MXN",
  "requestedAt": "2026-05-10T18:22:10.000Z",
  "acceptedAt": "2026-05-10T18:22:35.000Z",
  "completedAt": "2026-05-10T18:54:22.000Z"
}
```

---

#### `POST /api/v1/me/trips/{id}/cancel` — Cancel your trip

Cancels a trip you requested. Only valid for statuses PENDING / SEARCHING / ASSIGNED / DRIVER_ARRIVED / WAITING_CUSTOMER / SCHEDULED. Once `IN_PROGRESS`, the operator must intervene. Cancellation penalties (where configured) are assessed in the legacy flow and not by this endpoint.

- **OperationId:** `cabgo_cancel_my_trip`
- **Scopes:** `rider.trips:cancel`
- **Audience:** `rider`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `id` | path | string | — |

**Request body example:**

```json
{
  "reason": "Cambio de planes"
}
```

**curl:**

```bash
curl -X POST -H "Authorization: Bearer $CABGO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Cambio de planes"}' \
  https://www.cabgo.app/api/v1/me/trips/{id}/cancel
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me/trips/${tripId}/cancel", { method: "POST", headers: { Authorization: `Bearer ${CABGO_TOKEN}`, "Content-Type": "application/json" }, body: JSON.stringify({ reason: "Cambio de planes" }) });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.post(
    "https://www.cabgo.app/api/v1/me/trips/{id}/cancel",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
    json={"reason":"Cambio de planes"},
)
data = res.json()
```

---

#### `GET /api/v1/me/addresses` — List saved addresses

Rider's saved addresses, sorted by most-recently used.

- **OperationId:** `cabgo_list_addresses`
- **Scopes:** `rider.profile:read`
- **Audience:** `rider`

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/me/addresses
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me/addresses", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/me/addresses",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

#### `POST /api/v1/me/addresses` — Save a new address

Idempotent on `(lat, lng)` — saving the same coordinate again bumps `usageCount` and `lastUsedAt` instead of creating a duplicate. `label` and `notes` are optional and overwrite on collision.

- **OperationId:** `cabgo_save_address`
- **Scopes:** `rider.addresses:write`
- **Audience:** `rider`

**Request body example:**

```json
{
  "address": "Av. Reforma 222, Cuauhtémoc, CDMX",
  "lat": 19.4326,
  "lng": -99.1332,
  "label": "Casa"
}
```

**curl:**

```bash
curl -X POST -H "Authorization: Bearer $CABGO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"address":"Reforma 222","lat":19.4326,"lng":-99.1332,"label":"Casa"}' \
  https://www.cabgo.app/api/v1/me/addresses
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me/addresses", { method: "POST", headers: { Authorization: `Bearer ${CABGO_TOKEN}`, "Content-Type": "application/json" }, body: JSON.stringify({ address: "Reforma 222", lat: 19.4326, lng: -99.1332, label: "Casa" }) });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.post(
    "https://www.cabgo.app/api/v1/me/addresses",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
    json={"address":"Reforma 222","lat":19.4326,"lng":-99.1332,"label":"Casa"},
)
data = res.json()
```

---

#### `GET /api/v1/me/wallet` — Read your wallet balance

Returns the wallet balance and the last 20 transactions. Works for both rider and driver tokens — the right wallet is chosen automatically from the `aud` claim.

- **OperationId:** `cabgo_my_wallet`
- **Scopes:** `rider.wallet:read`, `driver.wallet:read`
- **Audience:** `rider` / `driver`

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/me/wallet
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me/wallet", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/me/wallet",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

**Response example:**

```json
{
  "balance": 480.5,
  "transactions": [
    {
      "id": "wtx_...",
      "type": "TRIP_PAYMENT",
      "amount": -180,
      "description": "Viaje T-A1B2C3",
      "createdAt": "2026-05-10T18:54:22.000Z"
    }
  ]
}
```

---

#### `GET /api/v1/me/payment-methods` — List saved payment methods

- **OperationId:** `cabgo_list_payment_methods`
- **Scopes:** `rider.payments:read`
- **Audience:** `rider`

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/me/payment-methods
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me/payment-methods", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/me/payment-methods",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

#### `GET /api/v1/me/orders` — List your delivery orders

Requires `tenant.deliveryEnabled = true`. Returns up to `limit` (default 30) of the rider's most recent orders.

- **OperationId:** `cabgo_my_orders`
- **Scopes:** `rider.orders:read`
- **Tenant feature:** `deliveryEnabled` must be `true`
- **Audience:** `rider`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `limit` | query | integer | — |

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/me/orders
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me/orders", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/me/orders",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

### Operator — Trips

#### `GET /api/v1/trips` — List tenant trips

Paginated list of trips in the active tenant. The token's `tenant_id` claim is enforced — a token bound to tenant A can never list tenant B's trips. Use `from` / `to` (ISO 8601) to scope a date range, and follow `next_cursor` to walk the rest of the result set.

- **OperationId:** `cabgo_list_trips`
- **Scopes:** `tenant.trips:read`
- **Permission:** `VIEW_TRIPS`
- **Audience:** `dashboard`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `status` | query | string (PENDING\|SEARCHING\|ASSIGNED\|DRIVER_ARRIVED\|IN_PROGRESS\|COMPLETED\|CANCELLED) | Filter by trip status. |
| `driverId` | query | string | Restrict to trips assigned to a specific driver. |
| `customerId` | query | string | Restrict to trips requested by a specific rider. |
| `from` | query | string | ISO 8601 lower bound on `requestedAt`. |
| `to` | query | string | ISO 8601 upper bound on `requestedAt`. |
| `limit` | query | integer | 1..200. Default 50. |
| `cursor` | query | string | Pass `next_cursor` from the previous page. |

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/trips?status=COMPLETED&from=2026-05-01T00:00:00Z&limit=50
```

**JavaScript:**

```javascript
const params = new URLSearchParams({ status: "COMPLETED", limit: "50" });
const res = await fetch("https://www.cabgo.app/api/v1/trips?${params}", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/trips",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
    params={"status": "COMPLETED", "limit": 50},
)
data = res.json()
for trip in data["items"]:
    print(trip["tripCode"], trip["finalFare"])
```

**Response example:**

```json
{
  "items": [
    {
      "id": "tr_01HK9F2ZTYP3JK4QXX7BD2N3V8",
      "tripCode": "T-A1B2C3",
      "status": "COMPLETED",
      "driverId": "drv_8H...",
      "customerId": "cust_2P...",
      "originAddress": "Av. Reforma 222, Cuauhtémoc, CDMX",
      "destAddress": "Aeropuerto CDMX T1",
      "estimatedFare": 245.5,
      "finalFare": 250,
      "currency": "MXN",
      "requestedAt": "2026-05-10T18:22:10.000Z",
      "acceptedAt": "2026-05-10T18:22:35.000Z",
      "completedAt": "2026-05-10T18:54:22.000Z"
    }
  ],
  "next_cursor": "tr_01HK9F2ZTYP3JK4QXX7BD2N3V8"
}
```

---

#### `GET /api/v1/trips/{id}` — Read a single trip

Full trip detail including the assigned driver, the rider, and the last 100 trip-log events.

- **OperationId:** `cabgo_get_trip`
- **Scopes:** `tenant.trips:read`
- **Permission:** `VIEW_TRIPS`
- **Audience:** `dashboard`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `id` | path | string | — |

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/trips/tr_01HK9F2ZTYP3JK4QXX7BD2N3V8
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/trips/${tripId}", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/trips/{trip_id}",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

**Response example:**

```json
{
  "id": "tr_01HK9F2ZTYP3JK4QXX7BD2N3V8",
  "tripCode": "T-A1B2C3",
  "status": "COMPLETED",
  "driverId": "drv_8H...",
  "customerId": "cust_2P...",
  "originAddress": "Av. Reforma 222, Cuauhtémoc, CDMX",
  "destAddress": "Aeropuerto CDMX T1",
  "estimatedFare": 245.5,
  "finalFare": 250,
  "currency": "MXN",
  "requestedAt": "2026-05-10T18:22:10.000Z",
  "acceptedAt": "2026-05-10T18:22:35.000Z",
  "completedAt": "2026-05-10T18:54:22.000Z"
}
```

---

### Operator — Drivers

#### `GET /api/v1/drivers` — List tenant drivers

Drivers in the active tenant with live status and last-known location. Use `status=ONLINE` for the live-dispatch view.

- **OperationId:** `cabgo_list_drivers`
- **Scopes:** `tenant.drivers:read`
- **Permission:** `VIEW_DRIVERS`
- **Audience:** `dashboard`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `status` | query | string (OFFLINE\|ONLINE\|BUSY) | — |
| `limit` | query | integer | — |
| `cursor` | query | string | — |

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/drivers?status=ONLINE
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/drivers?status=ONLINE", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/drivers?status=ONLINE",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

**Response example:**

```json
{
  "items": [
    {
      "id": "drv_8H4F9PNXVMK7Q5RW2L0DZB1AC3",
      "firstName": "Juan",
      "lastName": "Pérez",
      "email": "juan@example.com",
      "phone": "+525500000000",
      "photoUrl": "https://...",
      "status": "ONLINE",
      "rating": 4.92,
      "totalTrips": 1248,
      "vehiclePlate": "ABC-123",
      "vehicleModel": "Nissan Versa",
      "vehicleColor": "Gris",
      "currentLat": 19.4326,
      "currentLng": -99.1332,
      "locationUpdatedAt": "2026-05-10T18:55:00.000Z"
    }
  ],
  "next_cursor": null
}
```

---

### Operator — Customers

#### `GET /api/v1/customers` — List + search customers

Cursor-paginated customer list. `q` matches substring against name, first name, last name, phone, or email (case-insensitive).

- **OperationId:** `cabgo_list_customers`
- **Scopes:** `tenant.customers:read`
- **Permission:** `VIEW_CUSTOMERS`
- **Audience:** `dashboard`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `q` | query | string | Search substring (name / phone / email). |
| `limit` | query | integer | — |
| `cursor` | query | string | — |

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/customers?q=maria
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/customers?q=maria", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/customers?q=maria",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

### Operator — Settings

#### `GET /api/v1/settings` — Read tenant settings

Company branding metadata + CompanySettings (pricing, payments, dispatch flags, feature toggles like `taxiEnabled` / `deliveryEnabled`).

- **OperationId:** `cabgo_get_settings`
- **Scopes:** `tenant.settings:read`
- **Permission:** `VIEW_SETTINGS`
- **Audience:** `dashboard`

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/settings
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/settings", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/settings",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

#### `GET /api/v1/services` — List tenant service types

Active ServiceTypes (taxi standard, premium, courier, etc.). Use the `id` of one of these when calling `POST /api/v1/me/trips`.

- **OperationId:** `cabgo_list_services`
- **Scopes:** `tenant.settings:read`
- **Permission:** `VIEW_SETTINGS`
- **Audience:** `dashboard`

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/services
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/services", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/services",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

#### `GET /api/v1/zones` — List dispatch zones

- **OperationId:** `cabgo_list_zones`
- **Scopes:** `tenant.settings:read`
- **Permission:** `VIEW_SETTINGS`
- **Audience:** `dashboard`

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/zones
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/zones", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/zones",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

#### `GET /api/v1/branding` — Read tenant branding

Logo, colors, theme, support / legal URLs.

- **OperationId:** `cabgo_get_branding`
- **Scopes:** `tenant.settings:read`
- **Permission:** `VIEW_BRANDING`
- **Audience:** `dashboard`

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/branding
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/branding", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/branding",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

#### `PATCH /api/v1/branding` — Update tenant branding

Patches one or more of `appName`, `primaryColor`, `secondaryColor`, `iconBackgroundColor`, `themeMode`, `logoUrl`, `faviconUrl`, `supportUrl`, `termsUrl`, `privacyUrl`. Set a field to `null` to clear it.

- **OperationId:** `cabgo_update_branding`
- **Scopes:** `tenant.branding:write`
- **Permission:** `EDIT_BRANDING`
- **Audience:** `dashboard`

**Request body example:**

```json
{
  "primaryColor": "#FF5722",
  "appName": "TaxisVip"
}
```

**curl:**

```bash
curl -X PATCH -H "Authorization: Bearer $CABGO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"primaryColor":"#FF5722"}' \
  https://www.cabgo.app/api/v1/branding
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/branding", {
  method: "PATCH",
  headers: { "Authorization": `Bearer ${CABGO_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ primaryColor: "#FF5722" }),
});
```

**Python:**

```python
import requests
res = requests.patch(
    "https://www.cabgo.app/api/v1/branding",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
    json={"primaryColor": "#FF5722"},
)
```

---

### Operator — Wallets & Reports

#### `GET /api/v1/reports/summary` — Tenant KPI summary

Trip counts by status, completed-trip revenue, count of currently-active drivers, total customers. Window defaults to the last 30 days.

- **OperationId:** `cabgo_reports_summary`
- **Scopes:** `tenant.reports:read`
- **Permission:** `VIEW_REPORTS`
- **Audience:** `dashboard`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `from` | query | string | ISO 8601 — default `now - 30d`. |
| `to` | query | string | ISO 8601 — default `now`. |

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/reports/summary
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/reports/summary", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/reports/summary",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

**Response example:**

```json
{
  "window": {
    "from": "2026-04-11T00:00:00Z",
    "to": "2026-05-11T00:00:00Z"
  },
  "tripsByStatus": {
    "COMPLETED": 1248,
    "CANCELLED": 31
  },
  "completedTrips": 1248,
  "revenue": 312440.5,
  "activeDrivers": 27,
  "totalCustomers": 4521
}
```

---

#### `GET /api/v1/wallets` — Read wallet totals

Default response is aggregated `drivers` + `customers` totals. Pass `?type=driver|customer&detail=1` for per-actor listings (top by balance, paginated).

- **OperationId:** `cabgo_wallet_totals`
- **Scopes:** `tenant.wallets:read`
- **Permission:** `VIEW_WALLETS`
- **Audience:** `dashboard`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `type` | query | string (driver\|customer) | — |
| `detail` | query | string (1) | — |
| `limit` | query | integer | — |

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/wallets
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/wallets", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/wallets",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

### Operator — Delivery

#### `GET /api/v1/businesses` — List delivery businesses

Returns active businesses in the tenant. Requires `tenant.deliveryEnabled = true`; otherwise responds with `403 feature_disabled`.

- **OperationId:** `cabgo_list_businesses`
- **Scopes:** `tenant.businesses:read`
- **Permission:** `VIEW_BUSINESSES`
- **Tenant feature:** `deliveryEnabled` must be `true`
- **Audience:** `dashboard`

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/businesses
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/businesses", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/businesses",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

#### `GET /api/v1/orders` — List delivery orders

FoodOrders across the tenant. Filterable by status, business, customer. Requires `tenant.deliveryEnabled = true`.

- **OperationId:** `cabgo_list_orders`
- **Scopes:** `tenant.delivery:read`
- **Permission:** `VIEW_FOOD_ORDERS`
- **Tenant feature:** `deliveryEnabled` must be `true`
- **Audience:** `dashboard`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `status` | query | string | — |
| `businessId` | query | string | — |
| `customerId` | query | string | — |
| `limit` | query | integer | — |
| `cursor` | query | string | — |

**curl:**

```bash
curl -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/orders
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/orders", { headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.get(
    "https://www.cabgo.app/api/v1/orders",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
data = res.json()
```

---

### Driver

#### `PATCH /api/v1/me/status` — Update driver status

Toggle online / offline. The body accepts either `{ online: true|false }` (convenience) or `{ status: 'ONLINE'|'OFFLINE'|'BUSY' }` (explicit). Side-effect free at this layer: location pings and dispatch enrollment still happen in the mobile-app path.

- **OperationId:** `cabgo_set_driver_status`
- **Scopes:** `driver.status:write`
- **Audience:** `driver`

**Request body example:**

```json
{
  "online": true
}
```

**curl:**

```bash
curl -X PATCH -H "Authorization: Bearer $CABGO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"online":true}' \
  https://www.cabgo.app/api/v1/me/status
```

**JavaScript:**

```javascript
await fetch("https://www.cabgo.app/api/v1/me/status", {
  method: "PATCH",
  headers: { "Authorization": `Bearer ${CABGO_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ online: true }),
});
```

**Python:**

```python
import requests
requests.patch(
    "https://www.cabgo.app/api/v1/me/status",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
    json={"online": True},
)
```

---

#### `POST /api/v1/me/trips/{id}/accept` — Accept an open trip

Driver takes ownership of a PENDING / SEARCHING / COLLECTING_OFFERS trip in their tenant. Transitions the trip to ASSIGNED and sets `acceptedAt`. Returns 409 if the trip is already assigned to a different driver.

- **OperationId:** `cabgo_accept_trip`
- **Scopes:** `driver.trips:accept`
- **Audience:** `driver`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `id` | path | string | — |

**curl:**

```bash
curl -X POST -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/me/trips/{id}/accept
```

**JavaScript:**

```javascript
await fetch("https://www.cabgo.app/api/v1/me/trips/" + tripId + "/accept", {
  method: "POST",
  headers: { "Authorization": `Bearer ${CABGO_TOKEN}` },
});
```

**Python:**

```python
import requests
res = requests.post(
    "https://www.cabgo.app/api/v1/me/trips/{id}/accept",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
```

---

#### `POST /api/v1/me/trips/{id}/arrived` — Mark as arrived at pickup

- **OperationId:** `cabgo_mark_arrived`
- **Scopes:** `driver.trips:complete`
- **Audience:** `driver`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `id` | path | string | — |

**curl:**

```bash
curl -X POST -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/me/trips/{id}/arrived
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me/trips/${tripId}/arrived", { method: "POST", headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.post(
    "https://www.cabgo.app/api/v1/me/trips/{id}/arrived",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
```

---

#### `POST /api/v1/me/trips/{id}/start` — Start the trip

Marks a DRIVER_ARRIVED trip as IN_PROGRESS (rider boarded).

- **OperationId:** `cabgo_start_trip`
- **Scopes:** `driver.trips:complete`
- **Audience:** `driver`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `id` | path | string | — |

**curl:**

```bash
curl -X POST -H "Authorization: Bearer $CABGO_TOKEN" \
  https://www.cabgo.app/api/v1/me/trips/{id}/start
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me/trips/${tripId}/start", { method: "POST", headers: { Authorization: `Bearer ${CABGO_TOKEN}` } });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.post(
    "https://www.cabgo.app/api/v1/me/trips/{id}/start",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
)
```

---

#### `POST /api/v1/me/trips/{id}/complete` — Complete the trip

Marks an IN_PROGRESS trip COMPLETED. Optional body overrides estimate-time values with measured ones — `finalFare` (number, currency units), `actualDistance` (km), `actualDuration` (seconds). Receipt + commission settlement happen in downstream listeners on the existing mobile path.

- **OperationId:** `cabgo_complete_trip`
- **Scopes:** `driver.trips:complete`
- **Audience:** `driver`

**Parameters:**

| Name | In | Type | Description |
| --- | --- | --- | --- |
| `id` | path | string | — |

**Request body example:**

```json
{
  "finalFare": 250,
  "actualDistance": 12.3,
  "actualDuration": 1920
}
```

**curl:**

```bash
curl -X POST -H "Authorization: Bearer $CABGO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"finalFare":250}' \
  https://www.cabgo.app/api/v1/me/trips/{id}/complete
```

**JavaScript:**

```javascript
const res = await fetch("https://www.cabgo.app/api/v1/me/trips/${tripId}/complete", { method: "POST", headers: { Authorization: `Bearer ${CABGO_TOKEN}`, "Content-Type": "application/json" }, body: JSON.stringify({ finalFare: 250 }) });
const data = await res.json();
```

**Python:**

```python
import requests
res = requests.post(
    "https://www.cabgo.app/api/v1/me/trips/{id}/complete",
    headers={"Authorization": f"Bearer {CABGO_TOKEN}"},
    json={"finalFare":250},
)
data = res.json()
```

---

## Error reference

All errors return JSON: `{ error, error_description, [scope|permission|feature] }`.

| Status | error | Meaning |
| --- | --- | --- |
| 401 | `unauthorized` | Token missing, invalid, or revoked. |
| 403 | `insufficient_scope` | Token doesn't carry the required scope. |
| 403 | `forbidden` | Operator's `Permission` was removed (re-checked per request). |
| 403 | `feature_disabled` | Tenant has the required feature off. |
| 404 | — | Resource missing OR present in another tenant (we don't distinguish). |
| 409 | `invalid_state` | Trip is in a status that disallows this action. |
