FikaChu Docs

FikaChu Ride Booker API

Partner integration guide for booking on-demand, prebooked, and flexible ridehail (and delivery) through FikaChu.

How to use these docs

  1. Start at the developer hub / for product cards and getting-started.
  2. Use this guide for auth, flows, curl examples, webhooks, and errors.
  3. Track wire cutovers in API_CHANGES.md (/guides/api-changes/).

This document is self-contained for partner integration.


Table of contents

  1. Introduction
  2. Getting access
  3. Environments
  4. Auth
  5. Ride types at a glance
  6. Endpoint reference
  7. On-demand rides
  8. Prebooked rides
  9. Flexible rides
  10. Delivery use case
  11. Webhooks
  12. Errors
  13. Sandbox checklist
  14. Related APIs
  15. FikaChu integration notes

1. Introduction

The Ride Booker API lets third-party platforms (resellers, corporate travel, e-commerce shipping, etc.) create and manage trips on FikaChu without embedding the rider mobile app.

Core pattern for every bookable category:

  1. EstimatePOST …/estimations with stops (and schedule when required) → receive categories[], each with one or more fare_id values.
  2. CreatePOST …/create with a chosen fare_id and the same stops → receive ride_id.
  3. Track — poll GET …/details (and on-demand GET …/location), and/or receive webhooks.
  4. FinishGET …/receipt after completion, or POST …/cancel while cancellation is allowed.

fare_id binds vehicle type and payment method. Do not send vehicle_type_id or payment_method on create.

All paths below are relative to your environment base URL (see Environments). Unless noted, every call requires a Bearer access token.


2. Getting access

  1. Contact your FikaChu representative to enable Ride Booker API access for your organisation.
  2. Obtain OIDC / OAuth client credentials and the identity issuer URL from your FikaChu contact.
  3. Confirm which payment methods (cash, wallet, …) and vehicle categories are enabled for your partnership.
  4. Optionally register HTTPS webhook endpoints your platform can receive (see Webhooks).

Your integration obtains Bearer tokens from that issuer. FikaChu validates them via OAuth2 token introspection and does not mint partner tokens itself.


3. Environments

Environment Base URL (example) Notes
Production https://api.example.com/ride-booker/v1/ Live trips and billing
Staging / sandbox https://staging.example.com/ride-booker/v1/ Non-production fleet and data

Replace the host with the value your FikaChu contact provides.

Resource Path (on API origin, not under /ride-booker/v1/)
Developer hub GET /
Ride Booker guide GET /guides/ride-booker/
API changes GET /guides/api-changes/
Health GET /_health

Example full on-demand estimate URL:

https://api.example.com/ride-booker/v1/rides/estimations


4. Auth

Every Ride Booker request must include:

Authorization: Bearer <access_token>
Content-Type: application/json

4.1 Obtaining a token

Token issuance is performed against the OIDC / OAuth issuer your FikaChu contact provides. A typical client credentials flow looks like:

curl --request POST 'https://oidc.example.com/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'client_id=YOUR_CLIENT_ID' \
  --data-urlencode 'client_secret=YOUR_CLIENT_SECRET' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'scope=openid'

Example successful response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600,
  "token_type": "Bearer",
  "scope": "openid"
}

Exact scope values and token lifetime depend on your issuer configuration. Cache the token and refresh before expiry (or retry once on 401).

4.2 Using the token

curl --request GET \
  'https://api.example.com/ride-booker/v1/rides/details?ride_id=550e8400-e29b-41d4-a716-446655440000' \
  --header 'Authorization: Bearer YOUR_TOKEN'

Missing, expired, or unintrospectable tokens → 401 Unauthorized.

4.3 Common authentication issues

Symptom Likely cause Fix
401 on all calls Missing/expired Bearer token, or issuer not accepted by FikaChu Refresh token; confirm issuer URL and client credentials with your FikaChu contact
403 on create with a foreign user.phone Caller is not authorised to book for that phone Use a phone belonging to the authenticated user, or request elevated access
Token works on issuer but API rejects it Audience / client not authorised for this API Confirm the token subject is allowed on this FikaChu deployment

5. Ride types at a glance

On-demand Prebooked Flexible
Use case Ride / delivery needed now Scheduled future pickup Pre-created request; passenger dispatches when ready
Estimate POST /rides/estimations POST /rides/prebooked/estimations POST /rides/flexible/estimations
Create POST /rides/create POST /rides/prebooked/create POST /rides/flexible/create
Details GET /rides/details GET /rides/prebooked/details GET /rides/flexible/details
Cancel POST /rides/cancel POST /rides/prebooked/cancel POST /rides/flexible/cancel
Key from estimate fare_id fare_id fare_id (same envelope as on-demand)
Fare TTL 300 seconds (5 minutes), single-use 300 seconds, single-use 86 400 seconds (24 hours) for flexible tokens
Time constraints Immediate pickup_time between 30 minutes and 90 days ahead Optional pickup_time / stops
Extra Location + receipt endpoints Paginated history Stops optional on create

Important: After a prebooked or flexible ride is dispatched into the live matching pool, continue tracking with the on-demand details / cancel / receipt paths using the same ride_id.

Lifecycle states (selected)

Values are the string status field on ride payloads (Ride FSM).

On-demand / in-progress

Status Meaning
REQUESTED Created, not yet searching
SEARCHING Looking for a driver
DRIVER_ASSIGNED Soft offer to a driver (may expire)
DRIVER_ON_ROUTE_TO_CLIENT Driver accepted / en route to pickup
ARRIVED_AT_CLIENT Driver at pickup
DRIVING_WITH_CLIENT Trip in progress (after start)
COMPLETED Finished successfully
CANCELLED Cancelled (system / partner / driver)
CLIENT_CANCELLED Cancelled by rider
CLIENT_DID_NOT_SHOW No-show
NO_DRIVER_FOUND Matching failed
PAYMENT_BOOKING_FAILED Payment / booking failure

Prebooked / flexible (scheduled)

Status Meaning
SCHEDULED Waiting for pickup window / dispatch
DRIVER_COMMITTED Driver committed ahead of time
READY_TO_DISPATCH Flexible: ready for passenger dispatch
DISPATCHED Handed off to on-demand-style matching
OTHER Unmapped / transitional

6. Endpoint reference

All paths relative to /ride-booker/v1/.

Method Path Type Purpose
POST /rides/estimations On-demand Quote categories × payment methods
POST /rides/create On-demand Create ride using fare_id
GET /rides/details?ride_id= On-demand Ride state, vehicle, stops, costs
GET /rides/location?ride_id= On-demand Live driver/vehicle GPS (active only)
GET /rides/receipt?ride_id= On-demand Itemised cost after completion
POST /rides/cancel On-demand Rider cancel
POST /rides/prebooked/estimations Prebooked Quote with future pickup_time
POST /rides/prebooked/create Prebooked Create scheduled ride
GET /rides/prebooked/details?ride_id= Prebooked Prebooked detail
POST /rides/prebooked/cancel Prebooked Cancel before on-demand handoff
GET /rides/prebooked/history Prebooked Upcoming prebooked rides (paginated)
POST /rides/flexible/estimations Flexible Categories + fare_id tokens
POST /rides/flexible/create Flexible Create flexible ride
GET /rides/flexible/details?ride_id= Flexible Flexible detail
POST /rides/flexible/cancel Flexible Cancel before dispatch

Coordinate convention

  • Fika request/response longitude shortform is **lon** (stops, nested booker vehicle, driver_location).
  • GBFS MobilityData feeds still use lon (separate contract).

Conventions shared by all creates

Field Required Notes
fare_id Yes From estimation; expires; single-use
stops Yes (except flexible may omit) Min 2 for on-demand/prebooked; first = pickup, last = final drop
user No { "phone", "name" } display metadata
notes No Message visible to driver
service_type No ridehail (default) or delivery
manifest_items No Cargo catalog (delivery)
webhook_url No HTTPS URL for status callbacks
pickup_time Prebooked yes; flexible optional ISO-8601 datetime

7. On-demand rides

Typical flow: Estimate → Create → Track → Receipt (or Cancel before pickup completion rules apply).

7.1 Estimate

POST /rides/estimations

Notes

  • stops: minimum 2 waypoints, visited in order.
  • Optional payment_methods (e.g. ["cash","wallet"]). Omit to use the intersection of rider-allowed methods across priced vehicle types (defaults to ["cash"] when empty).
  • Do not send vehicle_type_id or a single top-level payment_method.
  • Each category × allowed payment method yields its own fare_id.
  • eta is seconds until nearest eligible driver (or trip routing duration when no online drivers).
  • Invalid payment methods for a category are omitted from estimations (not a 400).

Request

{
  "stops": [
    { "lat": -6.7924, "lon": 39.2083, "address": "Masaki, Dar es Salaam" },
    { "lat": -6.8161, "lon": 39.2803, "address": "Julius Nyerere Int'l Airport" }
  ],
  "payment_methods": ["cash", "wallet"]
}

curl

curl --request POST 'https://api.example.com/ride-booker/v1/rides/estimations' \
  --header 'Authorization: Bearer YOUR_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "stops": [
      {"lat": -6.7924, "lon": 39.2083},
      {"lat": -6.8161, "lon": 39.2803}
    ],
    "payment_methods": ["cash"]
  }'

Response 200

{
  "categories": [
    {
      "vehicle_type": {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "name": [{"text": "Economy", "language": "en"}],
        "short_description": [{"text": "Affordable everyday rides", "language": "en"}],
        "description": [{"text": "…", "language": "en"}],
        "vehicle_assets": {
          "icon_url": "https://www.example.com/assets/icon_car.svg",
          "icon_url_dark": "https://www.example.com/assets/icon_car_dark.svg",
          "icon_last_modified": "2021-06-15"
        },
        "rider_capacity": 4,
        "propulsion_type": "combustion",
        "cargo_volume_capacity": 2000,
        "cargo_load_capacity": 1000,
        "max_range_meters": 80,
        "vehicle_image": "https://www.example.com/assets/icon_car_dark.svg"
      },
      "pickup_estimate": 240,
      "no_vehicles_available": false,
      "available_vehicle_count": 3,
      "trip": {
        "distance_estimate": 5.0,
        "distance_unit": "km",
        "duration_estimate": 600
      },
      "estimations": [
        {
          "payment_method": "cash",
          "fare_id": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.example-fare-token",
          "expires_at": 1730000000,
          "price": {
            "amount": "12500–12500 TZS",
            "minimum_amount": 12500.0,
            "maximum_amount": 12500.0,
            "currency_code": "TZS"
          }
        }
      ]
    }
  ]
}

Response fields

Field Meaning Unit / notes
categories[] One quote group per bookable vehicle type
vehicle_type.id Vehicle type UUID; bound into fare_id (do not send on create) UUID
vehicle_type.name Localized display name { "text", "language" }[]
vehicle_type.short_description Localized short blurb for list UIs { "text", "language" }[]
vehicle_type.description Localized longer description { "text", "language" }[]
vehicle_type.vehicle_assets Icon URLs for light/dark UI GBFS-style object; icon_last_modified is a date string (YYYY-MM-DD)
vehicle_type.rider_capacity Maximum passengers count
vehicle_type.propulsion_type Powertrain GBFS enum (e.g. combustion, electric)
vehicle_type.cargo_volume_capacity Cargo volume when set liters (nullable)
vehicle_type.cargo_load_capacity Max cargo mass when set kg (nullable)
vehicle_type.max_range_meters Rated vehicle range when set meters (nullable)
vehicle_type.vehicle_image Product / hero image URL string
pickup_estimate ETA for nearest eligible driver to reach pickup seconds; null when no_vehicles_available or for prebooked/flexible
no_vehicles_available No eligible online fleet for this type near pickup bool; on-demand only (false on prebooked/flexible)
available_vehicle_count Eligible pool size after busy/radius filters (before routing) count; 0 on prebooked/flexible
trip.distance_estimate Routed pickup→dropoff distance number; see distance_unit
trip.distance_unit Unit for distance_estimate typically km
trip.duration_estimate Routed trip time pickup→dropoff seconds
estimations[] One bookable quote per allowed payment method omitted methods are not bookable for this type
estimations[].payment_method How the rider will pay e.g. cash,business, wallet
estimations[].fare_id Opaque token for POST …/rides/create single-use; invalid after expires_at
estimations[].expires_at When fare_id becomes invalid unix seconds (UTC); on-demand ~300s TTL
estimations[].price.amount Display fare band string including currency code
estimations[].price.minimum_amount Lower fare bound major currency units
estimations[].price.maximum_amount Upper fare bound major currency units
estimations[].price.currency_code Fare currency ISO 4217 (e.g. TZS)

Store the chosen estimations[].fare_id for create. Prefer expires_at over hard-coding TTL.

7.2 Create

POST /rides/create

Notes

  • fare_id is mandatory, single-use, and expires after 5 minutes.
  • Stops should match the estimation route (same coordinates and order).
  • Optional user.phone / user.name for driver communication; authenticated user remains ride owner unless Developer rules apply.
  • On success, persist ride_id for all subsequent calls.

Request

{
  "fare_id": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.example-fare-token",
  "stops": [
    { "lat": -6.7924, "lon": 39.2083, "address": "Masaki, Dar es Salaam" },
    { "lat": -6.8161, "lon": 39.2803, "address": "JNIA Terminal 3" }
  ],
  "user": { "phone": "+255700000000", "name": "Asha Mwangi" },
  "notes": "Please call on arrival",
  "webhook_url": "https://partner.example.com/hooks/fikachu-rides"
}

Response 201

{
  "ride_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "SEARCHING",
  "service_type": "ridehail",
  "booking_type": "on_demand",
  "stops": [
    {
      "order": 0,
      "lat": -6.7924,
      "lon": 39.2083,
      "address": "Masaki, Dar es Salaam",
      "notes": ""
    },
    {
      "order": 1,
      "lat": -6.8161,
      "lon": 39.2803,
      "address": "JNIA Terminal 3",
      "notes": ""
    }
  ],
  "estimated_cost": "12500.00",
  "currency": "TZS",
  "payment_method": "cash",
  "notes": "Please call on arrival",
  "created": "2026-08-03T12:00:00.000000Z",
  "user": { "phone": "+255700000000", "name": "Asha Mwangi" }
}

Error examples

  • Invalid / expired / already consumed fare_id400 { "detail": "Invalid or expired fare_id. Request a new estimation." }
  • Foreign phone without Developer group → 403

7.3 Details (track)

GET /rides/details?ride_id=<uuid>

Returns the same RideSerializer shape as create. Poll no more often than about once every 10 seconds unless your agreement says otherwise; prefer webhooks for status fan-out.

curl --request GET \
  'https://api.example.com/ride-booker/v1/rides/details?ride_id=550e8400-e29b-41d4-a716-446655440000' \
  --header 'Authorization: Bearer YOUR_TOKEN'

When a vehicle is assigned, vehicle may look like:

{
  "vehicle_id": "veh_public_123",
  "vehicle_type": {
    "vehicle_type_id": "…",
    "name": "Economy",
    "form_factor": "car",
    "rider_capacity": 4
  },
  "lat": -6.795,
  "lon": 39.21,
  "vehicle_image": null,
  "color": "white",
  "vehicle_plate_no": "T123ABC"
}

and driver_location:

{ "lat": -6.795, "lon": 39.21 }

7.4 Location

GET /rides/location?ride_id=<uuid>

Lightweight live position for active rides only.

Response 200

{
  "lat": -6.8012,
  "lon": 39.2145,
  "last_updated": "2026-08-03T12:05:01.000000Z"
}

Suitable for map polylines (e.g. up to ~1 Hz). Inactive / wrong status → 400.

7.5 Receipt

GET /rides/receipt?ride_id=<uuid>

Call after COMPLETED. Receipt may appear immediately or shortly after finalization (finalize_pending: true until cost breakdown exists).

Response 200

{
  "ride_id": "550e8400-e29b-41d4-a716-446655440000",
  "amount": 12500.0,
  "amount_paid": 0.0,
  "amount_due": 12500.0,
  "currency_code": "TZS",
  "invoice_url": null,
  "invoice_uuid": null,
  "fare_breakdown": [
    { "type": "ride_fee", "amount": 12000.0 },
    { "type": "booking_fee", "amount": 500.0 }
  ],
  "fare_lines": [],
  "finalize_pending": false,
  "payment_status": "pending"
}

7.6 Cancel

POST /rides/cancel

Request

{
  "ride_id": "550e8400-e29b-41d4-a716-446655440000",
  "reason": "Passenger changed plans"
}

Response 200

{
  "ride_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "CANCELLED"
}

Cancellation is only allowed in certain in-flight statuses (on-demand: through ARRIVED_AT_CLIENT, not after driving with client / completed). Wrong state → 400 with detail. Rider cancel via this API sets status to **CANCELLED**.


8. Prebooked rides

Same estimate → create pattern with a future pickup_time.

8.1 Estimate

POST /rides/prebooked/estimations

  • pickup_time required: ≥ 30 minutes and ≤ 90 days from now.
  • Stops: min 2, same rules as on-demand.
  • Response envelope identical: { "categories": [ … fare_id … ] }.
  • Fare TTL: 300 seconds.

Request

{
  "pickup_time": "2026-08-10T07:30:00+03:00",
  "stops": [
    { "lat": -6.7924, "lon": 39.2083 },
    { "lat": -6.8161, "lon": 39.2803 }
  ],
  "payment_methods": ["cash"]
}

Error if pickup_time is out of window → 400 { "detail": "pickup_time must be at least 30 minutes in the future." } (or the max-ahead message).

8.2 Create

POST /rides/prebooked/create

Include the same pickup_time and stops used for the fare. Optional delivery / webhook fields apply as on on-demand create.

Request

{
  "fare_id": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.example-prebooked-fare",
  "pickup_time": "2026-08-10T07:30:00+03:00",
  "stops": [
    { "lat": -6.7924, "lon": 39.2083, "address": "Hotel lobby" },
    { "lat": -6.8161, "lon": 39.2803, "address": "JNIA" }
  ],
  "user": { "name": "Asha Mwangi", "phone": "+255700000000" },
  "notes": "Flight AB123"
}

Response 201 — same ride shape; typically booking_type: "prebooked", status: "SCHEDULED", pickup_time set.

If server auto-dispatch is enabled, the ride may later move into matching (SEARCHING / on-demand path). After handoff, use on-demand details/cancel/receipt with the same ride_id.

8.3 Details / cancel / history

Call Notes
GET /rides/prebooked/details?ride_id= Full ride payload
POST /rides/prebooked/cancel Body { "ride_id", "reason"? } — before on-demand handoff
GET /rides/prebooked/history?page=1&page_size=20 Upcoming rides for the current user

History response 200 (paginated)

{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "ride_id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "SCHEDULED",
      "service_type": "ridehail",
      "booking_type": "prebooked",
      "pickup_time": "2026-08-10T04:30:00Z",
      "stops": [],
      "vehicle": null,
      "driver_location": null,
      "estimated_cost": "12500.00",
      "currency": "TZS",
      "total_cost": null,
      "payment_method": "cash",
      "start_time": null,
      "end_time": null,
      "notes": "Flight AB123",
      "manifest_items": [],
      "manifest_actions": [],
      "created": "2026-08-03T12:00:00.000000Z",
      "user": { "phone": "+255700000000", "name": "Asha Mwangi" }
    }
  ]
}

9. Flexible rides

Flexible rides pre-create a booking token / ride the passenger can use when ready.

9.1 Estimate

POST /rides/flexible/estimations

Stops and pickup_time are optional. Response still uses the shared { "categories": […] } envelope with **fare_id** per vehicle type × payment method (not a separate vehicle-type-only booking field).

Flexible fare tokens use a longer TTL: 86 400 seconds (24 hours).

Request (minimal)

{
  "payment_methods": ["cash"]
}

Request (with stops)

{
  "stops": [
    { "lat": -6.7924, "lon": 39.2083 },
    { "lat": -6.8161, "lon": 39.2803 }
  ],
  "pickup_time": "2026-08-03T18:00:00+03:00",
  "payment_methods": ["cash"]
}

Response — same shape as on-demand estimations.

9.2 Create

POST /rides/flexible/create

fare_id required. Stops optional; if omitted, placeholder coordinates may be used until updated.

Request

{
  "fare_id": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.example-flexible-fare",
  "stops": [
    { "lat": -6.7924, "lon": 39.2083 },
    { "lat": -6.8161, "lon": 39.2803 }
  ],
  "user": { "name": "Asha Mwangi", "phone": "+255700000000" }
}

Response 201 — ride with booking_type: "flexible", often SCHEDULED / READY_TO_DISPATCH depending on configuration.

9.3 Details / cancel

  • GET /rides/flexible/details?ride_id=
  • POST /rides/flexible/cancel with { "ride_id", "reason"? } before on-demand dispatch

After dispatch, use on-demand endpoints with the same ride_id.


10. Delivery use case

Delivery rides use the same estimate/create endpoints. On create, set:

  • service_type: "delivery"
  • manifest_items[]: cargo catalog (external_id is the stable id)
  • stops[].actions[]: pickup | dropoff | return with item_external_ids listing catalog external_ids
  • Optional stops[].verification_requirements for proof of delivery

Quantities must balance across the trip (pickup / dropoff / return). Unknown requirement keys → 400.

10.1 Create delivery example

Estimate first with the same stop coordinates (actions are not required on estimate). Then:

Request

{
  "fare_id": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.example-fare-token",
  "service_type": "delivery",
  "notes": "Handle with care",
  "webhook_url": "https://shop.example.com/api/webhook/shipping/",
  "manifest_items": [
    {
      "external_id": "BOX-1",
      "name": "Cell phone box",
      "quantity": 1,
      "weight": 30,
      "price": 10000,
      "dimensions": { "length": 40, "width": 40, "height": 40 },
      "is_fragile": true
    },
    {
      "external_id": "LAP123",
      "name": "Laptop",
      "quantity": 2,
      "weight": 3000,
      "price": 12000,
      "dimensions": { "length": 30, "width": 10, "height": 20 }
    }
  ],
  "stops": [
    {
      "lat": -6.78652,
      "lon": 39.259711,
      "address": "Warehouse A",
      "notes": "Pickup parcels",
      "actions": [
        { "type": "pickup", "item_external_ids": ["BOX-1", "LAP123"] }
      ]
    },
    {
      "lat": -6.798702,
      "lon": 39.255937,
      "address": "Customer B",
      "notes": "Dropoff phone box",
      "actions": [
        { "type": "dropoff", "item_external_ids": ["BOX-1"] }
      ],
      "verification_requirements": {
        "pincode": { "enabled": true, "value": "4242" },
        "picture": { "enabled": true },
        "signature": { "enabled": true, "collect_signer_name": true },
        "barcodes": {
          "enabled": true,
          "barcodes": [{ "type": "CODE128", "value": "BOX-1" }]
        }
      }
    },
    {
      "lat": -6.78652,
      "lon": 39.259711,
      "address": "Warehouse A",
      "notes": "Return laptops",
      "actions": [
        { "type": "return", "item_external_ids": ["LAP123"] }
      ]
    }
  ],
  "user": { "name": "Shop Desk", "phone": "+255711111111" }
}

Response 201 (excerpt)

{
  "ride_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "status": "SEARCHING",
  "service_type": "delivery",
  "booking_type": "on_demand",
  "manifest_items": [
    {
      "external_id": "BOX-1",
      "name": "Cell phone box",
      "quantity": 1.0,
      "weight": 30.0,
      "length": 40.0,
      "width": 40.0,
      "height": 40.0,
      "price": 10000.0,
      "is_fragile": true,
      "must_be_upright": false
    }
  ],
  "manifest_actions": [
    {
      "stop_order": 0,
      "action": "pickup",
      "item_external_ids": ["BOX-1", "LAP123"]
    },
    {
      "stop_order": 1,
      "action": "dropoff",
      "item_external_ids": ["BOX-1"]
    },
    {
      "stop_order": 2,
      "action": "return",
      "item_external_ids": ["LAP123"]
    }
  ],
  "stops": [
    {
      "order": 1,
      "lat": -6.798702,
      "lon": 39.255937,
      "address": "Customer B",
      "notes": "Dropoff phone box",
      "verification_requirements": {
        "pincode": { "enabled": true, "value": "4242" },
        "picture": { "enabled": true },
        "signature": { "enabled": true, "collect_signer_name": true },
        "barcodes": {
          "enabled": true,
          "barcodes": [{ "type": "CODE128", "value": "BOX-1" }]
        }
      },
      "verification": {}
    }
  ]
}

10.2 Verification requirement keys

Only these top-level keys are allowed:

Key When enabled
pincode Requires value (string the driver must match)
picture Driver uploads a photo
signature Optional collect_signer_name, collect_signer_relationship
barcodes barcodes: [{ "type", "value" }] — types: CODE39, CODE39_FULL_ASCII, CODE128, QR

10.3 Partner expectations during fulfillment

Partners do not upload PoD media on the booker API. Drivers collect evidence via the driver app:

  • POST /drivers/v1/trips/{ride_id}/stops/{stop_order}/verification-media
  • PATCH /drivers/v1/trips/{ride_id} with arrive_at_stop / complete and verification

Partners should:

  1. Poll GET /rides/details (or consume webhooks) for status and stop verification blobs.
  2. Treat completed delivery when status is COMPLETED and required stop verifications are present.

After driver start, FikaChu advances current_stop_index past pickup when a next stop exists (drivers are not asked to re-confirm stop 0).


11. Webhooks

11.1 Registering a URL

Pass webhook_url on create (HTTPS recommended):

"webhook_url": "https://shop.example.com/api/webhook/shipping/"

FikaChu POSTs JSON when ride status events are delivered. Retries: up to 3 attempts with delay on failure.

11.2 Envelope

Outbound body matches the realtime envelope:

{
  "schema": "fika.v1",
  "type": "ridebooker",
  "event_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "event_time": 1722686400,
  "event_type": "rides_ride.status_changed",
  "meta": {
    "ride_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "SEARCHING",
    "service_type": "delivery",
    "user_id": "42"
  }
}
Field Description
schema Envelope version (fika.v1)
type Always ridebooker
event_id UUID for idempotent processing
event_time Unix seconds
event_type e.g. rides_ride.status_changed
meta Event payload; includes user_id string and ride fields

Headers: Content-Type: application/json. Respond with 2xx quickly; slow or non-2xx responses are retried.

  1. Deduplicate on event_id.
  2. Update your order from meta.status / meta.ride_id.
  3. Optionally confirm with GET /rides/details before showing terminal states to end users.
  4. Do not expose your webhook URL publicly without verification (network allowlists, shared secret at your edge, etc.). FikaChu ride webhooks currently POST without a platform HMAC header—protect the endpoint yourself.

12. Errors

12.1 Shapes

Field validation:

{
  "stops": ["Ensure this field has at least 2 elements."],
  "fare_id": ["This field is required."]
}

Generic:

{ "detail": "Invalid or expired fare_id. Request a new estimation." }

Nested verification errors may appear under keys such as verification_requirements, pincode, barcodes.

12.2 HTTP status codes

Code When
200 / 201 Success
400 Validation, expired fare, invalid state (cancel / location), bad service_type
401 Missing or invalid Bearer token
403 Foreign rider phone without Developer group
404 Ride not found for this user
5xx Unexpected server / dependency failure

12.3 Common issues

Issue Guidance
Fare rejected on create Re-estimate; ensure stops and (for prebooked) pickup_time match; create within TTL
Empty categories No priced vehicle types / payment methods for this partnership
Cancel rejected Ride already in a non-cancellable status — check details
Location 400 Ride not in an active status
Manifest 400 Unbalanced pickup/dropoff/return or unknown external_id

13. Sandbox checklist

Use the staging / sandbox host your FikaChu contact provides:

  1. Obtain a Bearer token from the issuer.
  2. POST /rides/estimations with two stops → pick a fare_id.
  3. POST /rides/create within 5 minutes → store ride_id.
  4. Poll GET /rides/details and/or receive a webhook.
  5. Optional: create a delivery with manifest + verification_requirements + webhook_url.
  6. On completion, GET /rides/receipt.

Not required for core Ride Booker integration; see API_CHANGES.md for details.

Mount Audience
/ride-booker/v1/rentals/ Micromobility / vehicle rental partners
/gbfs/v1/ Public GBFS feeds (vehicle discovery; no auth)
/drivers/v1/ Driver enrollment, trip actions, PoD media, nearby rentals, rental-vehicles/{id}/ping-vehicle

Rental find/ring: POST /ride-booker/v1/rentals/{ride_id}/ping-vehicle (in session) or POST /drivers/v1/rental-vehicles/{vehicle_id}/ping-vehicle (browse). Body { lat, lon }; refused beyond 200 m (ping_too_far). Do not send typed alarmArm. Errors: 400 (not configured / no equipment / too far), 404 (no device), 502 (IoT delivery). See API_CHANGES.md (2026-08-15).


15. FikaChu integration notes

Topic FikaChu behavior
Create key Always **fare_id** from categories[].estimations[] (not vehicle type alone)
Driver note field **notes**
Longitude Use **lon** on FikaChu JSON (stops, vehicles on booker/driver/rental APIs). Rental gps_trail is [[lon, lat], …].
Payment on estimate Optional payment_methods[]; omit for defaults
Fare TTL 300s on-demand/prebooked; 86400s flexible
Delivery First-class via service_type, manifests, verification_requirements
Webhooks Create-time webhook_url; envelope schema / type: ridebooker / event_type / meta

Support

Include when contacting FikaChu support: environment host, UTC timestamp, ride_id / fare_id (if any), HTTP status, and response detail or validation body.