FikaChu Docs

API client contract

Current HTTP contracts that matter for third-party integrators.
For a full partner narrative with curl and end-to-end examples, see
FIKACHU_RIDE_BOOKER_API.md (/guides/ride-booker/).
Developer hub: /.

Conventions

Topic Rule
Auth Authorization: Bearer <access_token> from your FikaChu-issued OIDC / OAuth client
Longitude shortform FikaChu JSON uses **lon** (stops, destination_filter, rental vehicle/location, driver location). Rental gps_trail is [[lon, lat], …] pairs. GBFS feeds keep MobilityData lon. GeoJSON centers stay [lon, lat] array order.
Content type JSON unless multipart (KYC, stop verification media)
Soft offers Unaccepted DRIVER_ASSIGNED trips are push/WebSocket only — not in GET /drivers/v1/active
Back-to-back While DRIVING_WITH_CLIENT, matching near dropoff may queue a next_trip on accept (when enabled for the deployment)
Offer timeout Soft offers expire after a short window (typically ~30 seconds); check offer_expires_at when present

2026-10-22 — Webhook payloads use data.resource only

Breaking: webhook payloads no longer repeat the entity ID as a top-level
data.<resource>_id field. Use data.resource:

"resource": { "type": "ride", "id": "550e8400-e29b-41d4-a716-446655440000" }
  • data.ride_id is removed from all ride events.
  • data.endpoint_id is removed from webhook.test; use
    data.resource: { "type": "webhook_endpoint", "id": ... }.
  • Fikachu-Api-Version is bumped to 2026-10-22.
  • details_url is unchanged and may still contain ride_id as a query
    parameter for the detail endpoint.
  • Existing stored WebhookEvent bodies retain their original shape/version;
    replaying an old delivery resends the stored body.

2026-10-21 — Webhooks v1; webhook_url removed

Breaking: the create-time webhook_url field is removed from on-demand,
prebooked, flexible, and rental create payloads. It is no longer stored on
Ride and existing values are dropped. Partners must register an endpoint once:

POST /ride-booker/v1/webhooks/endpoints
{ "url": "https://...", "event_types": ["ride.status_changed"] }

The response returns the signing secret once. Deliveries are HMAC-SHA256 signed
with Fikachu-Signature: t=<unix>,v1=<hex> over {unix}. + raw body. Endpoint
CRUD, secret rotation, test events, delivery history, and replay are available
under /ride-booker/v1/webhooks/.

event_types may be omitted, [], "*", or ["*"] to receive all current and
future events. An explicit list subscribes only to those event types.

See Webhooks for the full integration and configuration
guide.

New event types: ride.created, ride.status_changed, ride.stop_completed,
ride.payment_updated, rental.session_changed, webhook.test.

New create fields:

  • external_id — partner order/reference, unique per caller when set.
  • metadata — small JSON object (max 4 KB), echoed in webhook payloads.

The create endpoints also accept Idempotency-Key for safe retries. The list
endpoint GET /ride-booker/v1/rides?updated_since=…&external_id=… supports
reconciliation after missed events.


2026-10-16 — Rental GPS trail: incremental upload + better source

At finalize the API no longer stores whatever the app last posted. It keeps the
best available geometry: the dense IoT route report while the tracker is healthy,
otherwise the rider breadcrumb. A phone that only captured two fixes used to beat
a 200+ position route report, which drew a straight line between unlock and lock
and made the phone_gps distance fallback under-measure the ride.

New: POST /ride-booker/v1/rentals/{ride_id}/trail — additive breadcrumb upload
while a rental runs (the trail was previously only sent with /complete).

{ "gps_trail": [[39.2083, -6.7924], [39.2085, -6.7926]] }
Status Meaning
200 { "ride_id": "...", "points": 12 } — stored point count
400 Ride cancelled, or the payload holds no valid [lon, lat] pair
404 Not your ride, not a vehicle rental, or no rental session

Uploads are monotonic: a payload with fewer points than the stored trail is
ignored, so a stale app cache can never truncate it. Rides whose billed distance
came from a degenerate trail — fewer than 3 points, no movement, or an unsampled
leg longer than 250 m — are flagged degenerate_gps_trail for ops review; the
charged amount is never recalculated.


2026-08-15 — Ping vehicle proximity (200 m)

Breaking. Both ping-vehicle endpoints require the driver's current coordinates. Ping is refused when farther than 200 m from vehicle.location.

{ "lat": -6.7924, "lon": 39.2083 }
Status code Meaning
400 ping_driver_location_required Missing/invalid lat/lon
400 ping_vehicle_location_unknown Vehicle has no location
400 ping_too_far Also distance_m, max_distance_m (200)

2026-08-15 — Ping vehicle (replaces browse Ring + typed alarmArm)

Breaking for driver / rental clients. Finding a vehicle no longer sends a typed IoT command (alarmArm via commands/send or POST …/rental-vehicles/{id}/ring).

Client Endpoint Body
Browse (no rental) POST /drivers/v1/rental-vehicles/{vehicle_id}/ping-vehicle { "lat", "lon" } (driver GPS; max 200 m)
Active rental POST /ride-booker/v1/rentals/{ride_id}/ping-vehicle { "lat", "lon" } (driver GPS; max 200 m)

Both require feat-iot-alarm and an iot_device_id. Response: { vehicle_id, device_id, iot_response }. There is no command_type field. /ring is removed (404).

The API looks up a saved Traccar command id from device (then group) attribute pingDeviceCommand and POSTs { "id", "deviceId" } to Traccar. Clients must not send alarmArm.

Status Meaning
200 Ping accepted
400 Disabled, missing feat-iot-alarm, rental not reserved/in-progress, pingDeviceCommand not configured, missing GPS, unknown vehicle location, or farther than 200 m (ping_too_far)
404 Vehicle / ride / IoT device missing
502 Traccar lookup or command delivery failed

Show Ring only when feat-iot-alarm is present (unchanged). Map 400 “not configured” and 502 to user copy; do not retry 4xx.


Rental estimation and rental vehicle payloads (nearby / details) expose vehicle_equipment as capability slugs. Clients gate UX as follows:

Slug When present When absent
feat-reserve-on-fikachu In-app Reserve Call to reserve (station contact_phone, else system phone_number)
feat-iot-alarm Show Ring (browse + active rental) Hide Ring
feat-iot-lock IoT lock on pause/end Hide IoT lock; show manual lock instructions
feat-iot-unlock IoT unlock to start Hide IoT unlock; show manual unlock instructions

Also:

  • Estimation vehicle includes station_id (GBFS station id) when docked.
  • Browse Ring (no active rental): POST /drivers/v1/rental-vehicles/{vehicle_id}/ping-vehicle — see 2026-08-15 ping-vehicle. Do not call /ring or commands/send with alarmArm.

2026-08-11 — Onboarding service subset + verified services

Breaking: Required document types resolve from active_service_types only (not supported). Empty active ⇒ no required docs.

Zero-doc auto-verify: After onboarding scope is set (region + vehicle type + active services) with no mandatory DocumentTypes, PATCH /drivers/v1/onboarding syncs is_verified=true and kyc_case_status=approved so the driver can enter the app without submit-for-review. POST /drivers/v1/onboarding/submit also approves immediately when the KYC policy is already met (including the zero-doc case).

PATCH /drivers/v1/onboarding when setting vehicle_type_id must include service_types (slugs/ids ⊆ that vehicle type’s services):

{
  "onboarding_state": { "vehicle_type_id": "<uuid>" },
  "service_types": ["delivery"]
}

Side effects: supported_service_types = all services linked to the vehicle type; active_service_types = the chosen subset.

GET /drivers/v1/onboarding adds supported_service_types / active_service_types (same brief shape as profile).

Profile service brief adds verified (bool): mandatory KYC for that service alone is fully approved (or no mandatory docs).

PATCH /drivers/v1/profile active_service_types: activating an unverified service returns 400 with:

{
  "active_service_types": [
    {
      "code": "service_kyc_incomplete",
      "service": "ridehail",
      "missing_document_types": [{ "id": 1, "slug": "driving_license", "upload_status": { "single": null }, "...": "..." }]
    }
  ]
}

Missing document payloads include upload_status (latest KYC status per side) so clients can show uploaded-but-pending vs not-uploaded. The same error also includes optional_document_types (skippable types for that service) so clients can list them with an Optional tag without blocking activation.

POST /drivers/v1/kyc/submit-review: After a verified driver uploads supplementary docs (activate-service walkthrough), queues the case for ops review:

  • kyc_case_status=approved → needs_additional_documents
  • kyc_case_status=draft → submitted

Returns { "kyc_case_status", "onboarding" }.


2026-08-11 — Driver remittance invoices + billing access gate

Breaking invoice shape for ridehail/delivery driver platform fees posted to FikaShop:

Before After
payer = billing partner, payer_wallet = partner wallet payer = driver, payee = partner, use_payer_wallet + use_payee_wallet (optional explicit payee_wallet when partner GET returns wallet_id)
Cash: commission lines only Cash: booking/service fees + tax + commission
Non-cash: commission only Unchanged (fee+tax already on rider invoice)

Driver profile (GET/PATCH /drivers/v1/profile) adds read-only:

"billing_access": {
  "allowed": true,
  "soft_warning": false,
  "owed_amount": 0,
  "currency": "TZS",
  "min_due": 0,
  "due_at": null,
  "unpaid_invoice_ids": [],
  "block_reason": null,
  "unpaid_invoice_uuids": [],
  "unpaid_unresolved_count": 0,
  "unpaid_statement_uuids": []
}

unpaid_invoice_uuids are public ids for GET /invoices/api/public/{uuid}/ pay flow (create-response uuid preferred, ext id fallback); unpaid_unresolved_count covers remittance rows with no FikaShop id (failed creates) that cannot be deep-linked; unpaid_statement_uuids is the subset that are consolidated statements (rendered as hero rows in the settlement sheet).

2026-09-23 — Driver fee statements (consolidated settlement)

New DriverFeeStatement model + billing/services/driver_statement.py:

  • POST /billing/v1/statements/ (driver auth) consolidates all uncovered unpaid rows — including failed-create rows with no invoice of their own — into one payable FikaShop invoice (fields.kind=driver_fee_statement, one line per trip). Idempotent while a statement is open (returns existing).
  • GET /billing/v1/statements/ returns the caller's open statement (or {"statement": null}).
  • Gate: covered rows leave the per-row gate; the statement total feeds it instead — every owed shilling is payable exactly once. sync_driver_fee_invoice skips covered rows (no second invoice for the same debt).
  • Partner-specific callbacks use /billing/v1/webhooks/fikashop/{webhook_route_key} and its /settlements path. The static alias routes only to the default or sole FikaShop partner. Statement refs are matched first (ext id or UUID), guarded against underpayment, then cascaded to covered rows.
  • Weekly beat task billing.tasks.generate_due_driver_fee_statements + statement pass inside the 15-min stale reconcile sweep (webhook fallback).
  • Live E2E: tests/test_driver_fee_statement_live.py (FIKASHOP_LIVE_TESTS=1 ... --reuse-db, pay probe additionally needs FIKASHOP_LIVE_PAY=1). Issuing tests need a valid partner token — refresh PricingPlanPartner.access_token from the FikaShop dashboard when admin writes 401.

Going online while hard-gated returns 403 with code: "driver_billing_blocked" and billing_access (both PATCH and PUT). Debt thresholds live on SystemRegion (driver_debt_soft_limit, driver_debt_hard_limit, driver_debt_grace_hours, driver_debt_min_payment_ratio).

Unlock rule: full remittance invoice settlement (driver_fee_payment_status=paid). min_due is advisory for UI only — partial payment does not clear the gate.

Hardening: driver-fee webhooks reject underpaid paid against remittance total (a low payload.total cannot bypass). Remittance create validates FikaShop total/amount_paid. Failed creates without an ext id still count toward owed until ops retries (--missing-driver-invoice-only). Matching also excludes soft past-due drivers via cached billing_due_at / owed even before billing_access_blocked is refreshed.

See OPERATIONS.md for remittance rules, retries (--missing-driver-invoice-only), partner token refresh, and Phase 2 digital setoff notes.


2026-08-10 — ImageUpload galleries + GBFS vehicle_assets removed (breaking)

Breaking feed change: vehicle_types.json (and ride estimation vehicle_type) no longer include vehicle_assets. Use vehicle_image for the identifying photo. Gallery photos are managed via the operator media API (Fleet Manager), not partner Ride Booker calls.

Surface Behavior
vehicle_assets on vehicle type Removed
Linked gallery images on vehicle type / station / pricing plan Via ImageUpload
Vehicle feature image Via ImageUpload
System brand icons Still via brand assets (light/dark)

Operator media API (Fleet Manager; not required for Ride Booker partners):

Method Path Notes
POST /gbfs/v1/images/ multipart file (+ optional caption, content_type, object_id)
GET /gbfs/v1/images/?content_type=&object_id= list linked photos
GET/DELETE /gbfs/v1/images/{id}/ detail / delete
POST /gbfs/v1/images/{id}/attach/ JSON {content_type, object_id}
POST /gbfs/v1/images/{id}/detach/ JSON {content_type, object_id}
POST /gbfs/v1/images/attach/ bulk link: JSON {content_type, object_id, image_ids: [...]}
POST /gbfs/v1/images/detach/ bulk unlink: same body; returns remaining linked photos

Allowlisted content_type values: gbfs.station, gbfs.vehicle_type, gbfs.pricing_plan, gbfs.vehicle_feature.
For gbfs.vehicle_feature (FK), bulk attach requires exactly one image_id.


2026-08-09 — Developer docs URL cutover (breaking for bookmarks)

Breaking path change for curated docs on the API host:

Resource Before After
Developer landing (none) /
Ride Booker partner guide markdown in repo only /guides/ride-booker/
API contract notes markdown in repo only /guides/api-changes/

Interactive OpenAPI (ReDoc, Swagger UI, /swagger.json) is no longer publicly exposed.


2026-08-09 — Driver trip privacy (breaking for driver clients)

Breaking wire change for driver list / detail / active trip payloads:

Field Behavior
rider_name Always first name + last initial (e.g. Jane D.)
rider_phone Present while trip is non-terminal or within ServiceType.driver_trip_contact_grace_hours after terminal (end_time, else last_updated); otherwise null
Stop address / lat / lon Exact while in contact window; after grace: coarsened street/area label and coords rounded to 3 decimal places
Ride / stop notes Cleared ("") after grace
Fare, distance, timestamps, payment Unchanged

Grace is configured per ServiceType.driver_trip_contact_grace_hours (default 48). Missing service type → grace 0 (immediate history redaction).

Cancelled trips: driver app hides name / notes / manifest on trip detail (UI-only in this release; API may still return them).


2026-08-11 — Longitude shortform lon + gps_trail pairs (breaking)

Breaking wire change: FikaChu JSON longitude keys are lon only (no lng alias). Compound shortcuts are pickup_lon / dropoff_lon.

Surface Before After
Ride-booker stops lng required lon required
Driver trip stops[] lng lon
destination_filter {lat, lng, …} {lat, lon, …}
Driver PUT /location, nearby rental query lng lon
Rental vehicle / complete location lng lon
Nested booker vehicle, trip-share vehicle_location lng lon
WS / trip shortcuts pickup_lng / dropoff_lng pickup_lon / dropoff_lon
Rental gps_trail [{lat, lng}, …] objects [[lon, lat], …] pairs (GeoJSON order)

Unchanged: GBFS feed lon, GeoJSON [lon, lat] order, Photon/Valhalla upstream params, promotions ?point=lon,lat.


2026-08-09 — Longitude shortform lng (breaking)

Historical: FikaChu JSON longitude keys were standardized on lng only (no lon alias). Superseded by 2026-08-11 — Longitude shortform lon.

Surface Before After
Ride-booker stops lon preferred; lon input alias lng required
Driver trip stops[] lon lng
destination_filter {lat, lon, …} {lat, lng, …}
Driver PUT /location, nearby rental query lon lng
Rental vehicle / complete location / gps_trail lon lng
Nested booker vehicle, trip-share vehicle_location lon lng

Unchanged at the time: GBFS feed lon, GeoJSON [lon, lat] order, Photon/Valhalla upstream params, pickup_lng / dropoff_lng.


2026-08-09 — Verification barcodes list key (breaking)

Breaking wire change: verification_requirements.barcodes uses nested barcodes instead of items:

"barcodes": {
  "enabled": true,
  "barcodes": [{ "type": "CODE128", "value": "BOX-1" }]
}

Driver-submitted verification.barcodes (scan evidence list) is unchanged.


2026-08-09 — Stop action item_external_ids (breaking)

Breaking wire change: ride-booker create (and any stop actions on estimate) use actions[].item_external_ids instead of actions[].items. Matches create/detail manifest_actions[].item_external_ids.


2026-08-09 — ServiceType.service_flow (breaking for UI clients)

Breaking wire change for driver profile / trip payloads that branch on product UX.

Field Role
slug / service_type Unique API identity (custom names allowed)
service_flow Behavior family: ridehail | delivery | vehicle_rental | rideshare
  • ServiceTypeBrief / vehicle service_types tags: add service_flow
  • Ride booker RideSerializer and driver trip payloads: add service_flow (from service_type.service_flow); service_type remains the slug
  • Bookable create resolves by slug but requires service_flow in {ridehail, delivery, rideshare}
  • Rentals / demand / analytics rental-only branch on service_flow, not slug equality

Clients must key home chrome and trip shells off service_flow, not hardcoded slug lists.


2026-08-09 — ServiceTypeBrief.image

ServiceTypeBrief (profile supported_service_types / active_service_types) adds image: absolute media URL for ServiceType.image, or "" when unset.


Breaking wire change for driver home demand map (same-day cutover from geohash rectangles).

Before After
Geohash precision-6 axis-aligned rectangles; id gh:…; properties.geohash H3 resolution-8 hex polygons; id h3:{cell}; properties.cell
Unbounded feature count in viewport Cap 60 features by pickup_count (busiest cores)
Parent geohash p4 + required viewport bbox Unchanged — still west,south,east,north (max 2°); intensity per parent

Query (unchanged)

GET /drivers/v1/demand-zones?west=39.15&south=-6.85&east=39.25&north=-6.75

Missing/invalid bbox → 400. Nav still uses properties.center [lon, lat].

Live heat FeatureCollection

{
  "type": "FeatureCollection",
  "generated_at": "2026-08-09T09:00:00Z",
  "window_minutes": 60,
  "features": [
    {
      "type": "Feature",
      "id": "h3:882a100d33fffff",
      "geometry": {
        "type": "Polygon",
        "coordinates": [[[39.21, -6.79], [39.215, -6.788], "...", [39.21, -6.79]]]
      },
      "properties": {
        "cell": "882a100d33fffff",
        "pickup_count": 14,
        "intensity": 0.82,
        "center": [39.208, -6.792]
      }
    }
  ]
}
  • Ring coordinates and center are [lon, lat]; ring is closed (first == last).
  • Tessellating hexes — no radius_m, no square geohash boxes.
  • Storage: DemandHotZone.cell_id (H3) + parent_geohash (p4) for regional index.

2026-08-09 — Demand hot zones: geohash index + viewport bbox (superseded)

Superseded by H3 hex + geohash-shard cutover above (same-day). Historical note: viewport bbox + parent geohash p4 indexing replaced the global Redis blob; geometry briefly used geohash-6 rectangles before hex restore.


2026-08-09 — Live demand hot zones → H3 hex polygons (historical)

Earlier same-day note: H3 Polygon cutover from Point + radius_m circles (then briefly geohash rectangles; see current H3 + shard section above).


2026-08-08 — ride_type → booking_type (breaking)

Breaking wire change after the Ride model redesign. Booking category is no longer named ride_type.

Before After Values (unchanged)
ride_type booking_type on_demand | prebooked | flexible

Surfaces

  • Ride-booker ride create/detail/list JSON (RideSerializer)
  • Driver GET /drivers/v1/trips (+ trip detail) (DriverTripSerializer)
  • Soft-offer WebSocket / push meta for rides_ride.driver_offer_ready

Cutover rules

  • No dual-key / legacy alias period: clients must read booking_type only.
  • External ride-booker or webhook consumers that still parse ride_type must switch in the same deploy window.

Unchanged client keys

  • pickup_time (prebooked/flexible)
  • offer_expires_at (soft-offer meta)
  • Rental: is_paused, pause_deadline, parking_photo_url, gps_trail
  • fulfillment_parent_ride_id / parent_ride_id

Ride booker (/ride-booker/v1/)

estimate → fare_id → create for on-demand, prebooked, and flexible. Same envelope on all three.

Estimate

Paths: POST …/rides/estimations, …/rides/prebooked/estimations, …/rides/flexible/estimations.

Request

  • stops[] with lat + **lon** (min 2 for on-demand/prebooked).
  • Optional payment_methods[] (e.g. ["cash", "wallet"]). Omit to use the intersection of rider-allowed methods (default ["cash"] when empty).
  • Prebooked/flexible: optional or required pickup_time as documented on each path.
  • Do not send vehicle_type_id or a single payment_method on estimate.

Response

{
  "categories": [
    {
      "vehicle_type": {
        "id": "<uuid>",
        "name": [{"text": "Economy", "language": "en"}],
        "short_description": [{"text": "…", "language": "en"}],
        "description": [{"text": "…", "language": "en"}],
        "vehicle_assets": {},
        "rider_capacity": 4,
        "propulsion_type": "combustion",
        "cargo_volume_capacity": null,
        "cargo_load_capacity": null,
        "max_range_meters": null,
        "vehicle_image": ""
      },
      "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": "<token>",
          "expires_at": 1730000000,
          "price": {
            "amount": "12500–12500 TZS",
            "minimum_amount": 12500.0,
            "maximum_amount": 12500.0,
            "currency_code": "TZS"
          }
        }
      ]
    }
  ]
}
  • **pickup_estimate**: seconds until nearest eligible driver at pickup; null when no_vehicles_available is true, or for prebooked/flexible (live fleet N/A).
  • **no_vehicles_available**: true when no eligible online vehicles for this type near pickup (on-demand only; always false for prebooked/flexible). Excludes busy drivers and vehicles beyond the match radius / max pickup ETA.
  • **available_vehicle_count**: eligible pool size after busy/radius filters (before routing); 0 for prebooked/flexible.
  • **trip.distance_estimate** / **trip.duration_estimate**: routed pickup→dropoff distance and duration (seconds).
  • **expires_at**: unix timestamp when the fare_id expires (matches fare cache TTL; on-demand ~5 minutes, flexible ~24h).
  • Invalid payment methods for a category are omitted from estimations (not a 400).
  • Flexible uses the same categories envelope (not a separate vehicle_categories shape).

Create

Paths: POST …/rides/create, …/rides/prebooked/create, …/rides/flexible/create.

Request (core)

{
  "fare_id": "<from chosen estimation>",
  "stops": [{ "lat": -6.79, "lon": 39.21 }],
  "user": { "phone": "+255700000000", "name": "Rider Name" },
  "notes": "optional",
  "service_type": "ridehail",
  "manifest_items": []
}
  • vehicle_type_id and payment_method come from the consumed fare_id — do not send them on create.
  • Optional user is display metadata (rider_display_name, rider_contact_phone); the authenticated user remains the ride owner unless a Developer-group foreign phone is used.
  • Ride-level driver message field is **notes** (not note_to_driver).
  • Flexible create: **fare_id required**; stops/notes/pickup_time optional as documented.
  • Prebooked create: scheduled ride; may auto-dispatch into matching when enabled.

Delivery / cargo

Optional on create:

  • service_type: ridehail (default) or delivery.
  • manifest_items[] catalog (external_id, name, quantity, …).
  • Per-stop actions: pickup | dropoff | return, each with item_external_ids referencing catalog external_ids. Quantities must balance across stops.
  • Per-stop verification_requirements — keys only: barcodes, picture, pincode, signature.

Example

{
  "fare_id": "<from estimation>",
  "service_type": "delivery",
  "manifest_items": [
    { "external_id": "BOX-1", "name": "Parcel", "quantity": 1, "is_fragile": true }
  ],
  "stops": [
    {
      "lat": -6.79,
      "lon": 39.21,
      "actions": [{ "type": "pickup", "item_external_ids": ["BOX-1"] }]
    },
    {
      "lat": -6.80,
      "lon": 39.28,
      "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" }]
        }
      }
    }
  ]
}

Barcode types: CODE39, CODE39_FULL_ASCII, CODE128, QR. Unknown requirement keys → 400.

Other booker paths: details, location, receipt, cancel.


Drivers (/drivers/v1/)

Profile preferences (GET|PATCH /drivers/v1/profile)

Writable preference fields (direct cutover; no legacy aliases):

Field Notes
active_service_types Matching allowlist (ids or slugs)
navigation_app / voice_guidance Client nav
avoid_tolls / avoid_highways Passed to OSRM / Google Maps deep links
rest_reminder_hours Client rest banner while continuously online
accepted_payment_methods JSON list; empty = all methods
destination_filter Nested object {lat, lon, address, radius_km, expires_at, one_shot}; null clears. Stored as GIS Point.
auto_accept_offers / auto_accept_max_pickup_eta_seconds Soft-offer auto ON_ROUTE
accept_long_trips / long_trip_min_distance_km Opt out of long crow-flies trips
max_match_radius_km Personal max crow-flies km vehicle→pickup; null = unlimited
active_vehicle When online, only this owned vehicle is online

Matching applies the vehicle type search radius, then filters on payment, destination, long-trip, and the driver's own max_match_radius_km preferences.

Read-only quality scores on profile:

Field Notes
acceptance_rate Percent accepts / (accepts + rejects + expiries); null until decided offers exist
cancellation_rate Percent driver cancels / (completed + driver cancels); null until closed trips exist
quality_score Composite 0–100 from rating, acceptance, and cancellations

Trips list and detail

  • GET /drivers/v1/trips — paginated history; filters: status, category (maps to booking_type, e.g. prebooked), period, from, to.
  • List/detail trip payloads include pickup_time (from RideSchedule; null when absent / on-demand) and tip_amount (from post-trip rating when set).
  • GET /drivers/v1/trips/{ride_id} — detail for the assigned driver (same path as PATCH). Includes list fields plus:
  • pickup_lat / pickup_lon / dropoff_lat / dropoff_lon
  • fare_breakdown (from TripCostBreakdown, or null)
  • rider_name, rider_phone
  • driver_has_rated_rider (ridehail) / platform rating flags as applicable
  • current_stop_index, stops[] (longitude as **lon**), manifests, verification blobs

Active workloads

GET /drivers/v1/active → { "trip": … | null, "next_trip": … | null, "rental": … | null }.

  • trip — current accepted ridehail/delivery (earliest start_time, else created)
  • next_trip — queued back-to-back trip when the driver accepted a second offer while on the current trip (at most one)
  • rental — newest active vehicle rental

Soft offers (DRIVER_ASSIGNED) are excluded until action=accept.

State transitions

PATCH /drivers/v1/trips/{ride_id}

{
  "action": "accept | reject_offer | arrive | driver_arrived | start | arrive_at_stop | complete | cancel | client_did_not_show | commit | decline_schedule",
  "driver_note": "optional",
  "verification": {}
}
Action Notes
accept Commit soft offer → on route; while on DRIVING_WITH_CLIENT, queues as next_trip (max one)
reject_offer Decline soft offer
arrive / driver_arrived Approach / arrived at client
start In progress; marks pickup complete and advances current_stop_index past stop 0 when a next stop exists
arrive_at_stop Mid-stop complete (not final); may require verification / driver_note; then increments index
complete Final stop; may require verification; triggers billing finalization
cancel Driver cancel
client_did_not_show Rider no-show from ARRIVED_AT_CLIENT → CLIENT_DID_NOT_SHOW
commit Accept scheduled prebook (SCHEDULED → commit → dispatch → on-demand match)
decline_schedule Decline scheduled prebook (SCHEDULED / DRIVER_COMMITTED → cancelled)

Rate trip

POST /drivers/v1/trips/{ride_id}/rate body: { "score": 1-5, "tags": [...], "tip_amount"?: number }.
Optional tip_amount is stored on RideRating and returned on trip list/detail as tip_amount.

200 includes at least ride_id, status, and usually current_stop_index.

Proof of delivery media

POST /drivers/v1/trips/{ride_id}/stops/{stop_order}/verification-media

  • Multipart: file + kind (picture | signature).
  • Pictures: JPEG/PNG/WebP; signatures: same + SVG.
  • Response includes image_url and merged verification — pass URLs in later PATCH verification.

Example verification evidence:

{
  "pincode": { "value": "4242" },
  "picture": { "image_url": "https://…" },
  "signature": { "image_url": "https://…", "signer_name": "Ada" },
  "barcodes": { "scanned": ["BOX-1"] }
}

Rentals (/ride-booker/v1/rentals/)

Post-trip billing (no fare_id). Use GBFS vehicle_id (public id).

Path Purpose
estimations / create Rate card / start session
{ride_id}/details Detail (GET /ride-booker/v1/rentals/{ride_id} is a compatibility alias)
{ride_id} PATCH Compat action: { "action": "end" } → stop workflow, status: "stopping"
{ride_id}/location, complete, cancel, receipt Lifecycle
{ride_id}/pause, resume, parking-photo, zones Session extras
{ride_id}/commands List IoT command types (owner-scoped)
{ride_id}/commands/send Send typed command: { "type", "attributes"? } (engine lock/unlock, etc.)
{ride_id}/ping-vehicle Find/ring vehicle (saved IoT ping; not alarmArm)

Rental command endpoints require Ride.user == request.user. Prefer these over standalone IoT command when acting inside a rental.


IoT (/iot/v1/)

Path Purpose
POST /iot/v1/command { "deviceId", "type", "attributes"?, "connection_id"? } — owner, driver, or staff
GET /iot/v1/session-token Vendor JWT + connection base_url / upload_url

Prefer rental-scoped command endpoints when acting inside a rental session. Device webhook ingest under /iot/v1/webhooks/… is for device backends, not partner Ride Booker clients.


Billing (/billing/v1/)

Partner-facing surfaces:

  • Trip cost breakdown and dispute endpoints under /billing/v1/.
  • Ride completion produces a receipt available on the Ride Booker receipt paths.

Payment provider callbacks under /billing/v1/webhooks/fikashop* are server-to-server (not partner ride webhooks). Configure the callback base URL and signing secret on the matching PricingPlanPartner, then use /ops/v1/billing-partners/{id}/sync-webhooks to upsert the partner-scoped FikaShop endpoints.