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-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
vehicleincludesstation_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/ringorcommands/sendwithalarmArm.
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_documentskyc_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
}
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/ vehicleservice_typestags: addservice_flow- Ride booker
RideSerializerand driver trip payloads: addservice_flow(fromservice_type.service_flow);service_typeremains the slug - Bookable create resolves by slug but requires
service_flowin{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
centerare[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_typeonly. - External ride-booker or webhook consumers that still parse
ride_typemust 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[]withlat+**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_timeas documented on each path. - Do not send
vehicle_type_idor a singlepayment_methodon 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;nullwhenno_vehicles_availableis 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);0for prebooked/flexible.**trip.distance_estimate** /**trip.duration_estimate**: routed pickup→dropoff distance and duration (seconds).**expires_at**: unix timestamp when thefare_idexpires (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
categoriesenvelope (not a separatevehicle_categoriesshape).
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_idandpayment_methodcome from the consumedfare_id— do not send them on create.- Optional
useris 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**(notnote_to_driver). - Flexible create:
**fare_idrequired**; stops/notes/pickup_timeoptional as documented. - Prebooked create: scheduled ride; may auto-dispatch into matching when enabled.
Delivery / cargo
Optional on create:
service_type:ridehail(default) ordelivery.manifest_items[]catalog (external_id,name, quantity, …).- Per-stop
actions:pickup|dropoff|return, each withitem_external_idsreferencing catalogexternal_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 tobooking_type, e.g.prebooked),period,from,to.- List/detail trip payloads include
pickup_time(fromRideSchedule;nullwhen absent / on-demand) andtip_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_lonfare_breakdown(fromTripCostBreakdown, ornull)rider_name,rider_phonedriver_has_rated_rider(ridehail) / platform rating flags as applicablecurrent_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 (earlieststart_time, elsecreated)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_urland mergedverification— pass URLs in later PATCHverification.
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 webhook URLs and secrets with your FikaChu contact.