Build the delivery.
We’ll drive it.
The RentADriver API connects your agent or app to local drivers. Quote a route, book a delivery, track progress and collect proof from one integration.
https://api.rentadriver.ai/v1claude mcp add --transport http rentadriver https://mcp.rentadriver.ai/mcpRoute-based quotes and traffic
Address-based quotes use planned route distance and journey time. Live traffic and departure-time predictions are included where available. Local pricing also accounts for peak hours, demand, vehicle, package size and urgency; a city-to-city quote uses the local rate card plus a drive-back allowance.
Package quantity, weight and volume are assessed together. Weight and dimensions are per package, multiplied by quantity across all item rows. Provide length_cm, width_cm and height_cm together; otherwise size-based volume estimates apply. Loads too large for the requested car automatically require a larger vehicle and its price multiplier. Additional handling time raises the price, the estimated completion time and the driver-pay floor for local and intercity deliveries.
Combined weight and volume, per-package weight, package count and all three dimensions are checked (rotation allowed). Five 20 kg packages need at least a car; bulky parcels may need a van. Scooter limits are 20 kg combined and 15 kg per package. The quote returns the selected vehicle and explains upgrades in vehicle_selection. Incomplete measurements return field-specific errors.
20 kg per package, 200 kg combined and 100 packages, subject to vehicle space and dimensions. These are service limits, not safe-lifting guarantees. Heavier or oversized loads return manual_review_required and cannot be booked automatically. Missing weights use S 1 kg, M 5 kg, L 10 kg and XL 20 kg estimates. Walking deliveries are not supported.
Inspect route_planned.source and route_planned.traffic_included before saying so. Bicycle routes do not include live road traffic; fallback and estimated routes are not live-traffic results. Traffic-aware durations receive no extra congestion multiplier.
Routes can be cached for up to two minutes; scheduled trips use forecasts for departure, not a guarantee of future conditions. The quoted route is reused at booking. Arrival estimates can update during delivery without changing the booked price. Enter pickup and drop-off addresses for a route-based quote.
Country workspaces and currencies
One account can deliver in multiple countries. Each enabled country has its own wallet, spending limits, local pricing, and eligible local drivers. UK deliveries use GBP; the US uses USD. No automatic currency conversion or transfers between wallets.
- 1Call
GET /v1/workspacesto see balances and available markets. An admin key can enable a country withPOST /v1/workspacesand{"country_code":"GB"}. - 2Get a quote. Its
workspaceidentifies the pickup country, currency, and whether that workspace is enabled. Public quotes do not create a workspace. - 3Fund the matching wallet using
POST /v1/wallet/depositwithworkspace_id. USDC deposits work only for USD workspaces. - 4Create the delivery with its quote ID. The pickup determines its workspace; optional
workspace_idchecks your intent. Holds, refunds, retries, tips, and driver earnings keep that currency.
Use X-Workspace-Id or ?workspace_id=… to select the console/API wallet, overview, spending controls, saved addresses, recurring schedules, or delivery list. Money endpoints without a selection use the original default workspace; the delivery list without a selection includes all workspaces. Keys and OAuth connections remain account-wide.
Setting a spending cap to zero blocks spending; null removes it. Daily caps cover a rolling 24 hours. Sandbox creates no wallet movements. Agents can use list_country_workspaces and enable_country_workspace, then pass the workspace ID to wallet and spending-control tools. If a wallet is short, fund that wallet: funds in another currency cannot cover the delivery.
Bank and crypto funding
In Console → Wallet, eligible accounts can use bank transfers in their workspace currency or USDC on Base and USDT on Ethereum for a USD workspace. Card funding remains available. Availability depends on account verification and supported payment services.
Complete secure identity or business verification, review the payment terms, then request payment instructions. Use the exact network, account details and reference shown, and send once. Funds become available after settlement, not when you open the payment page or submit a transfer.
Use GET /v1/wallet/bridge, POST /v1/wallet/bridge/verify and POST /v1/wallet/bridge/deposits with an admin key and X-Workspace-Id. For crypto, include from_address: a wallet you control on the selected network, also used for any refund. Supply a unique request_id UUID and retain it on retries. Poll the status response for credited_at and the actual credited_cents. Crypto credit reflects the settled value after provider fees. Sandbox does not accept real deposits.
Quick start
Use a sandbox key to test. Before a live booking, verify your account and fund your wallet—the delivery price is held when you book.
# 1. Price it — no auth
curl -X POST https://api.rentadriver.ai/v1/quotes -H 'Content-Type: application/json' -d '{
"pickup": {"address": "Borough Market, London", "contact_name": "Stall 12"},
"dropoffs": [{"address": "King's Cross Station, London", "contact_name": "Alex", "contact_phone": "+447700900000"}],
"items": [{"description": "Box of pastries", "size_class": "M", "category": "food"}],
"urgency": "asap"
}'
# 2. Get a sandbox key (valid 24 h, simulated drivers, nothing charged)
curl -X POST https://api.rentadriver.ai/v1/sandbox/keys
# → {"success": true, "key": "rd_test_…", "mode": "test", "expires_at": "…"}
# Your own test + live keys: POST /v1/signup or the Console
# 3. Book it — dispatch starts (live keys hold the price from your wallet)
curl -X POST https://api.rentadriver.ai/v1/deliveries -H 'x-api-key: rd_test_…' -H 'Content-Type: application/json' \
-d '{"quote_id": "<quote.id>", "instructions": "Ring twice", "idempotency_key": "order-1042"}'
# 4. Follow it
curl https://api.rentadriver.ai/v1/deliveries/<id>/track -H 'x-api-key: rd_test_…'
curl -N https://api.rentadriver.ai/v1/deliveries/<id>/events?stream=1 -H 'x-api-key: rd_test_…' # SSE
# 5. Proof, then confirm (auto after 24h)
curl https://api.rentadriver.ai/v1/deliveries/<id>/proof -H 'x-api-key: rd_test_…'
curl -X POST https://api.rentadriver.ai/v1/deliveries/<id>/confirm -H 'x-api-key: rd_test_…'Step 1 answers with the price before anything is booked (trimmed; values vary with time of day and traffic):
{
"success": true,
"quote": {
"id": "c5fc57d1-…",
"service_kind": "local",
"area": { "slug": "london", "name": "London", "currency": "GBP" },
"price": "47.90",
"client_price_cents": 4790,
"currency": "GBP",
"distance_m": 6272,
"duration_s": 2109,
"eta_status": "awaiting_driver_availability",
"breakdown": { "distance_km": 6.27, "duration_min": 48, "size_multiplier": 1.15, "urgency_multiplier": 1.25, "surge": 1, … },
"expires_at": "2026-09-23T11:03:32Z"
}
}The same three calls in Python and TypeScript:
import requests
API = "https://api.rentadriver.ai/v1"; KEY = "rd_test_..." # sandbox key (POST /v1/sandbox/keys); swap for rd_live_... when you go live
quote = requests.post(f"{API}/quotes", json={
"pickup": {"address": "Borough Market, London", "contact_name": "Stall 12"},
"dropoffs": [{"address": "King's Cross Station, London", "contact_name": "Alex", "contact_phone": "+447700900000"}],
"items": [{"description": "Box of pastries", "size_class": "M", "category": "food"}],
"urgency": "asap"}).json()["quote"]
delivery = requests.post(f"{API}/deliveries", headers={"x-api-key": KEY},
json={"quote_id": quote["id"], "external_ref": "order-1042"}).json()["delivery"]
status = requests.get(f"{API}/deliveries/{delivery['id']}", headers={"x-api-key": KEY}).json()["delivery"]
print(status["status"], status["tracking_url"])Authentication
Send your key as x-api-key: rd_live_… (or Authorization: Bearer rd_live_…). Keys are created from the Console, the POST /v1/signup endpoint, or the MCP signup tool. Up to 10 active keys per account; revoke from DELETE /v1/api-keys/:id. Quotes, coverage, service areas, stats and recipient tracking need no key.
Statuses
| Status | Meaning | Money |
|---|---|---|
| quoted | Created, not funded | — |
| funded | Scheduled; dispatch starts 45 min before the window | held |
| dispatching | Offering to on-shift drivers in expanding radius waves | held |
| assigned | A driver accepted | held |
| en_route_pickup → at_pickup → picked_up | Driver steps with pickup photo | held |
| en_route_dropoff → at_dropoff → delivered | Drop-off proof captured | held |
| confirmed / paid | You confirmed (or 24h passed); driver is paid | captured |
| no_driver_found | Six waves, no acceptance | released |
| cancelled | You cancelled. Free before assignment; a cancellation fee after | released (minus fee) |
| failed | Driver could not complete; a review is opened | held until resolved |
| disputed | You opened a dispute | held until resolved |
| returned | Item brought back to pickup | captured |
Pricing model
Local delivery quotes use the pickup city’s pricing profile, the planned route, the departure time and current driver supply. Rates are in that workspace’s currency. Use an address-based quote for current pricing on your route.
drive_minutes = routed_minutes × congestion
total_minutes = drive_minutes + handling_minutes_per_stop × (1 + dropoffs) + additional_load_handling_minutes
subtotal = base + per_km × km + per_min × total_minutes
+ per_extra_dropoff × max(0, dropoffs − 1)
price_before_floor = max(city_minimum, subtotal)
× size × urgency × vehicle × surge × peak_multiplierThe minimum is applied before the multipliers. The engine then checks the driver-pay and platform-margin floor, and rounds the client price and driver pay up to the next 10 minor currency units. Use the returned client_price_cents and driver_pay_cents rather than rebuilding the final amount from rounded breakdown fields.
Traffic, peak hours and driver demand
| Adjustment | How it works | Where to inspect it |
|---|---|---|
| Route and traffic | Motor-vehicle routes use TomTom traffic and departure-time predictions where available. When traffic is already included, congestion is 1: the engine does not charge for the same delay twice. TomTom results are not given an extra learned congestion adjustment. Other motor-vehicle fallback routes can use the city’s time-of-day congestion estimate, bounded to 0.85–1.60. Walking and cycling use congestion 1 and do not include live road traffic. | route_planned.source, route_planned.traffic_included, breakdown.congestion |
| Peak or quiet hours | A separate time-of-day price factor uses the pickup city’s timezone and the scheduled departure, or now for an unscheduled quote. The current time-profile logic produces 0.90–1.25: up to 10% below normal in quiet hours or 25% above normal in peak hours. It uses local history blended with a default curve where history is limited; there is no universal fixed “hot hours” timetable. | time.local_time, time.label, time.source, breakdown.peak_multiplier |
| Current supply and demand | Surge responds to open demand versus nearby supply. With open demand and no nearby supply, the starting factor is 1.30; otherwise excess demand adds 0.10 per job above supply. The result is bounded by the city’s surge cap, 1.80 by default. A higher price does not guarantee driver acceptance. | breakdown.supply_nearby, breakdown.demand_open, breakdown.surge |
| Package, urgency and vehicle | City-configured multipliers account for the package size, requested service urgency and the eligible vehicle. Each stop includes a handling-time allowance; each drop-off after the first also adds an extra-stop charge. | breakdown.size_multiplier, urgency_multiplier, vehicle_multiplier, stops_cents |
| Driver-pay floor | Driver pay is the greater of the configured price share and the estimated time-plus-mileage floor. If necessary, the client price rises to preserve the configured minimum platform margin. These are quote calculations, not guaranteed hourly earnings. | breakdown.driver_floor_cents, driver_share_cents, margin_floor_applied |
Scheduling, quote expiry and changing ETAs
Quotes expire after 15 minutes; check expires_at and obtain a new quote after expiry. To compare departure times, request a quote with the desired scheduled_at. An unscheduled, non-TomTom local quote may include up to three cheaper_slots within the next 12 hours when estimated savings are at least 5%. Scheduled quotes and TomTom quotes do not currently return these suggestions. A suggested slot is not a reserved price or a promise of future driver availability.
After booking, fresh driver GPS can update the remaining route and ETA. These traffic updates do not automatically reprice the booked delivery. Missing or stale GPS limits live estimates. City-to-city deliveries use the model below (the local rate card plus a drive-back allowance, with no surge, peak or ASAP multiplier).
City to city, same day
Set service to auto (default) or intercity on POST /v1/quotes or POST /v1/deliveries. When the pickup is in one launch city and the last drop-off is inside, or within 20 km of, a neighbouring launch city in the same country at most 160 km away by road, the quote comes back with service_kind: "intercity" and an intercity object: the city pair, departure_at (pickup), arrival_by and the transit time. One dedicated driver, same day, up to 3 drop-offs. Trips longer than 160 km answer 422 too_far: a parcel network is the cheaper choice for those.
A driver collects within about an hour. The delivery must arrive by 21:00 local time; an unscheduled booking too late for that is collected at 07:00 the next morning, and a scheduled_at that cannot make it answers 422 after_same_day_cutoff with the latest workable pickup. There is one service: the old speed values are still accepted and all mean same_day.
The origin city's local rate card on the real road route (base + per km + per minute + extra stops); past the first 20 km and 25 minutes the open road is priced at 35 % of the per-km and 50 % of the per-minute rate. Plus a drive-back allowance of half the one-way distance and driving time at those open-road rates, × size × vehicle. No surge, peak or ASAP multiplier. The components are in breakdown, the allowance in breakdown.intercity.return_cents.
The full price is held at booking, like a local delivery, and settles on confirm. Cancellation after assignment costs 20 % of the price; none after pickup.
GET /v1/intercity/lanes lists every city's neighbouring cities within 160 km with the road distance, drive time and a same-day sample price. MCP: list_intercity_lanes, get_intercity_quote.
Proof of delivery
Each stop declares proof_required. The driver app cannot complete a stop without the required proof, and GET /v1/deliveries/:id/proof returns signed URLs valid for one hour.
photo. Default: photo.
photo, signature, recipient_name or otp. Default: photo + recipient name. Recipient identity checks are not yet supported.
Generate a six-digit code with POST /v1/deliveries/:id/stops/:stop/code and share it privately with the recipient. Codes never appear in driver responses.
Webhooks
Register with POST /v1/webhooks or pass webhook_url per delivery. Prefer SSE (?stream=1) when your agent is long-running.
Each POST carries X-RentADriver-Event and X-RentADriver-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, t + "." + rawBody).
Failed deliveries are retried after 1 m, 5 m, 30 m, 2 h and 12 h. The fifth failure on a URL raises a notice to the account.
Events
Shopify
Merchants install the RentADriver app for Shopify from Shopify admin (or from /shopify).
It registers a carrier service that answers checkout with a live Same-day by RentADriver rate for addresses inside the shop’s radius, books the delivery on orders/paid (created_via: "shopify", external_ref = order name, metadata.shopify carries the order id), fulfils the order with the tracking link at pickup and posts fulfillment events until delivered.
List and book Shopify orders with GET /v1/shopify/orders and POST /v1/shopify/orders/:ref/book (order name like #1042; orders the app never saw are fetched from Shopify). MCP: list_shopify_orders, book_shopify_order.
Errors
| HTTP | code | What to do |
|---|---|---|
| 400 | validation_error / bad_request | Fix the payload; details lists the paths |
| 401 | unauthorized / invalid_api_key | Send a valid x-api-key |
| 402 | insufficient_funds | Deposit (card checkout URL or x402), then POST /fund |
| 403 | spending_cap | Raise the cap in account controls |
| 409 | invalid_state / conflict / already_taken | Read status and next_action; do not retry blindly |
| 410 | quote_expired / offer_expired | Request a new quote |
| 422 | outside_coverage / prohibited_item / proof_required | Change the request; see details |
| 429 | rate_limited | Back off; headers include X-RateLimit-Remaining |
Prohibited items
Creation is refused with 422 prohibited_item. Food is allowed as a food category without a temperature guarantee.
MCP
Install in one line. npx -y rentadriver-mcp exposes every endpoint below as a tool, plus create_store_pickup, resources and the plan_delivery prompt.
claude mcp add rentadriver -- npx -y rentadriver-mcpcodex mcp add rentadriver -- npx -y rentadriver-mcpgemini mcp add rentadriver npx -y rentadriver-mcpcode --add-mcp '{"name":"rentadriver","command":"npx","args":["-y","rentadriver-mcp"]}'Cursor, Windsurf, Claude Desktop and every other client: see the per-client commands.
delivery://, coverage://, openapi://spec and the plan_delivery prompt ship with the server.
https://mcp.rentadriver.ai/mcp with an x-api-key header, or OAuth from hosts that support it.
Reference
Support
GET/v1/support/ticketsThis account's support tickets, newest activity firstkey
Params: statusoffsetlimit
Responses: 200 OK
POST/v1/support/ticketsOpen a support ticket with RentADriver ops (optionally about one delivery)key
{
"type": "object",
"required": [
"subject",
"body"
],
"properties": {
"category": {
"type": "string",
"enum": [
"payment",
"delivery",
"account",
"app",
"safety",
"other"
]
},
"subject": {
"type": "string",
"minLength": 3,
"maxLength": 200
},
"body": {
"type": "string",
"minLength": 3,
"maxLength": 5000
},
"delivery_id": {
"type": "string",
"format": "uuid"
}
}
}Responses: 200 OK
GET/v1/support/tickets/{id}One ticket with its conversation (ops private notes excluded)key
Params: id
Responses: 200 OK
POST/v1/support/tickets/{id}/messagesReply on a ticketkey
Params: id
{
"type": "object",
"required": [
"body"
],
"properties": {
"body": {
"type": "string",
"minLength": 1,
"maxLength": 5000
},
"request_id": {
"type": "string",
"format": "uuid",
"description": "Idempotency: reuse after a timeout"
}
}
}Responses: 200 OK
GET/v1/support/assistantWhether the account-aware AI assistant is available, plus the guest chat link and support emailkey
Responses: 200 OK
POST/v1/support/identityOne-time ES256 assertion that signs the embedded AI assistant in as this organization (subject org:<organization id>, 120 s, verify with /.well-known/driver-support-jwks.json). Allowed for viewer keys; 30/min per keykey
{
"type": "object",
"additionalProperties": false,
"required": [
"nonce"
],
"properties": {
"nonce": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{32,200}$",
"description": "Fresh per request"
}
}
}Responses: 201 OK400 Missing or malformed nonce, or URL parameters sent503 Account-aware assistant switched off; tickets still work
GET/v1/support/contextRead-only support snapshot for this organization: status, one wallet balance per country workspace (never summed across currencies), 5 recent deliveries (city only) and 10 ticket summaries. No arguments; sandbox keys see sandbox deliveries onlykey
Responses: 200 OK
Account
GET/v1/account/overview30-day delivery and spending overview in the selected workspace currencykey
Params: workspace_id
Responses: 200 OK
GET/v1/workspacesList country workspaces, separate local-currency balances, and supported marketskey
Responses: 200 OK
POST/v1/workspacesEnable a zero-balance country workspace (admin role). Existing balances are never convertedkey
{
"type": "object",
"required": [
"country_code"
],
"properties": {
"country_code": {
"type": "string",
"enum": [
"US",
"GB",
"AU",
"CA"
]
},
"name": {
"type": "string"
},
"spending_cap_per_delivery_cents": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"daily_cap_cents": {
"type": [
"integer",
"null"
],
"minimum": 0
}
}
}Responses: 200 Workspace already enabled201 OK403 Admin role required422 Unsupported market
GET/v1/signup/verifyVerify email from the link sent at signup; unlocks wallet funding
Params: token
Responses: 200 OK
POST/v1/signupCreate an account + API key (shown once); email verification unlocks funding. Rehearse with a sandbox (rd_test_) key first
{
"type": "object",
"required": [
"name",
"email"
],
"properties": {
"country_code": {
"type": "string",
"enum": [
"US",
"GB",
"AU",
"CA"
],
"default": "US"
},
"name": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"kind": {
"type": "string",
"enum": [
"agent",
"app",
"business"
]
},
"agent_framework": {
"type": "string"
}
}
}Responses: 201 OK
GET/v1/accountAccount, balance, capabilitieskey
Params: workspace_id
Responses: 200 OK
PATCH/v1/account/controlsSpending capskey
Params: workspace_id
{
"type": "object",
"properties": {
"spending_cap_per_delivery_cents": {
"type": "integer",
"nullable": true
},
"daily_cap_cents": {
"type": "integer",
"nullable": true
}
}
}Responses: 200 OK
GET/v1/api-keysList keyskey
Responses: 200 OK
POST/v1/api-keysCreate key (max 10)key
Responses: 201 OK
DELETE/v1/api-keys/{id}Revoke keykey
Params: id
Responses: 200 OK
Wallet
GET/v1/wallet/ledgerSelected workspace ledger; every row includes currency and workspace_idkey
Params: workspace_id
Responses: 200 OK
GET/v1/walletBalance + recent ledgerkey
Params: workspace_id
Responses: 200 OK
GET/v1/wallet/bridgeBridge funding methods, verification and settlement history for the selected workspace (live admin key)key
Params: workspace_id
Responses: 200 OK403 Administrator key and verified email required409 Sandbox funding is not allowed
POST/v1/wallet/bridge/verifyStart identity or business verification for bank and crypto fundingkey
Params: workspace_id
{
"type": "object",
"required": [
"type",
"legal_name"
],
"properties": {
"type": {
"type": "string",
"enum": [
"individual",
"business"
]
},
"legal_name": {
"type": "string",
"minLength": 2,
"maxLength": 150
}
}
}Responses: 200 OK409 Complete verification already started in another workspace503 Provider unavailable
POST/v1/wallet/bridge/refreshRefresh Bridge verification and currency-specific banking eligibilitykey
Params: workspace_id
Responses: 200 OK
POST/v1/wallet/bridge/depositsPrepare bank or crypto deposit instructions; balance credits only after verified settlementkey
Params: workspace_id
{
"type": "object",
"additionalProperties": false,
"required": [
"request_id",
"amount_cents",
"method"
],
"properties": {
"from_address": {
"type": "string",
"description": "Required for crypto: wallet you control on Base (USDC) or Ethereum (USDT); also the return destination"
},
"request_id": {
"type": "string",
"format": "uuid",
"description": "Retain on retries; never reuse with changed details"
},
"amount_cents": {
"type": "integer",
"minimum": 2000,
"maximum": 500000
},
"method": {
"type": "string",
"enum": [
"bank",
"usdc",
"usdt"
],
"description": "Bank in workspace currency (GBP/USD); USDC Base and USDT Ethereum only for USD workspaces"
}
}
}Responses: 200 OK202 Request saved; poll GET /v1/wallet/bridge for instructions and credited_at409 Verification required, idempotency mismatch or request in progress422 Unsupported country/currency or invalid payment input503 Live funding unavailable
POST/v1/wallet/bridge/deposits/{id}/cancelCancel unused deposit instructions before funds arrivekey
Params: workspace_idid
Responses: 200 OK409 Payment is already processing or instructions are not ready
POST/v1/wallet/depositCard deposit via Stripe Checkout (returns a URL for a human to pay)key
{
"type": "object",
"required": [
"amount_cents"
],
"properties": {
"workspace_id": {
"type": "string",
"format": "uuid",
"description": "Country workspace owned by this account. Quotes/bookings infer it from pickup when omitted; money endpoints default to the original workspace. Never converts currency."
},
"amount_cents": {
"type": "integer",
"minimum": 500,
"maximum": 1000000
},
"success_url": {
"type": "string",
"description": "https; {CHECKOUT_SESSION_ID} is appended as session_id when absent"
},
"cancel_url": {
"type": "string"
}
}
}Responses: 200 OK501 deposits_not_configured
GET/v1/wallet/depositsCard top-ups for the selected workspace with status, invoice and receipt linkskey
Params: workspace_idlimit
Responses: 200 OK
GET/v1/wallet/deposits/{id}One card top-up (open | paid | credited | expired | failed | refunded); settles it from Stripe if the webhook has not arrived yetkey
Params: id
Responses: 200 OK404 not_found
POST/v1/x402/wallet/depositFund a USD country workspace via USDC on Base; other currencies rejectedkey
Params: workspace_id
{
"type": "object",
"properties": {
"amountCents": {
"type": "integer"
}
}
}Responses: 200 OK402 Payment requirements (x402 v2)404 x402_not_enrolled
Routing
GET/v1/routes/previewRoad geometry between 2–9 points (lat,lng;lat,lng) for mapskey
Params: points
Responses: 200 { success, route: GeoJSON LineString, distance_m, duration_s, source }
Tracking
GET/v1/track/{code}/routePlanned + remaining route and live driver position for a tracking codekey
Params: code
Responses: 200 { success, status, live, planned, remaining, driver, stops }
GET/v1/deliveries/{id}/trackLive status, ETA, driver positionkey
Params: id
Responses: 200 OK
GET/v1/deliveries/{id}/eventsEvent timeline; add ?stream=1 for SSEkey
Params: idstream
Responses: 200 OK
POST/v1/deliveries/{id}/stops/{stop}/codeGenerate or replace a private six-digit recipient code for an OTP-required stopkey
Params: idstop
Responses: 200 OK422 Stop does not require a code
GET/v1/deliveries/{id}/proofProof of delivery (signed photo URLs, signature, recipient)key
Params: id
Responses: 200 OK
GET/v1/events/streamSSE feed of every event on your accountkey
Responses: 200 text/event-stream
GET/v1/track/{code}Public recipient tracking by short code
Params: code
Responses: 200 OK
Deliveries
GET/v1/deliveries/{id}/routePlanned + remaining route and live driver position (console maps)
Params: id
Responses: 200 { success, status, live, planned, remaining, driver, stops }
POST/v1/deliveriesCreate (and fund) a deliverykey
{
"anyOf": [
{
"required": [
"quote_id"
]
},
{
"required": [
"pickup",
"dropoffs"
]
}
],
"type": "object",
"properties": {
"workspace_id": {
"type": "string",
"format": "uuid",
"description": "Country workspace owned by this account. Quotes/bookings infer it from pickup when omitted; money endpoints default to the original workspace. Never converts currency."
},
"pickup": {
"type": "object",
"required": [],
"properties": {
"address": {
"type": "string",
"description": "Street address; geocoded server-side if lat/lng omitted"
},
"lat": {
"type": "number"
},
"lng": {
"type": "number"
},
"contact_name": {
"type": "string"
},
"contact_phone": {
"type": "string"
},
"instructions": {
"type": "string"
},
"window_start": {
"type": "string",
"format": "date-time"
},
"window_end": {
"type": "string",
"format": "date-time"
},
"proof_required": {
"type": "array",
"items": {
"type": "string",
"enum": [
"photo",
"signature",
"recipient_name",
"otp"
]
}
}
}
},
"dropoffs": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "object",
"required": [],
"properties": {
"address": {
"type": "string",
"description": "Street address; geocoded server-side if lat/lng omitted"
},
"lat": {
"type": "number"
},
"lng": {
"type": "number"
},
"contact_name": {
"type": "string"
},
"contact_phone": {
"type": "string"
},
"instructions": {
"type": "string"
},
"window_start": {
"type": "string",
"format": "date-time"
},
"window_end": {
"type": "string",
"format": "date-time"
},
"proof_required": {
"type": "array",
"items": {
"type": "string",
"enum": [
"photo",
"signature",
"recipient_name",
"otp"
]
}
}
}
}
},
"service": {
"type": "string",
"enum": [
"auto",
"local",
"intercity"
],
"default": "auto",
"description": "auto picks intercity when the drop-off is in a neighbouring launch city: same-day, one dedicated driver, up to 160 km by road. Longer trips are refused (too_far)."
},
"speed": {
"type": "string",
"enum": [
"same_day",Responses: 201 OK402 insufficient_funds — deposit first403 spending_cap422 outside_coverage / prohibited_item
GET/v1/deliveriesList deliverieskey
Params: statussincelimitoffset
Responses: 200 OK
POST/v1/deliveries/batchCreate up to 25 deliverieskey
{
"type": "object",
"properties": {
"deliveries": {
"type": "array",
"items": {
"anyOf": [
{
"required": [
"quote_id"
]
},
{
"required": [
"pickup",
"dropoffs"
]
}
],
"type": "object",
"properties": {
"workspace_id": {
"type": "string",
"format": "uuid",
"description": "Country workspace owned by this account. Quotes/bookings infer it from pickup when omitted; money endpoints default to the original workspace. Never converts currency."
},
"pickup": {
"type": "object",
"required": [],
"properties": {
"address": {
"type": "string",
"description": "Street address; geocoded server-side if lat/lng omitted"
},
"lat": {
"type": "number"
},
"lng": {
"type": "number"
},
"contact_name": {
"type": "string"
},
"contact_phone": {
"type": "string"
},
"instructions": {
"type": "string"
},
"window_start": {
"type": "string",
"format": "date-time"
},
"window_end": {
"type": "string",
"format": "date-time"
},
"proof_required": {
"type": "array",
"items": {
"type": "string",
"enum": [
"photo",
"signature",
"recipient_name",
"otp"
]
}
}
}
},
"dropoffs": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "object",
"required": [],
"properties": {
"address": {
"type": "string",
"description": "Street address; geocoded server-side if lat/lng omitted"
},
"lat": {
"type": "number"
},
"lng": {
"type": "number"
},
"contact_name": {
"type": "string"
},
"contact_phone": {
"type": "string"
},
"instructions": {
"type": "string"
},
"window_start": {
"type": "string",
"format": "date-time"
},
"window_end": {
"type": "string",
"format": "date-time"
},
"proof_required": {
"type": "array",
"items": {
"type": "string",
"enum": [
"photo",
"signature",
"recipient_name",
Responses: 200 OK
GET/v1/deliveries/{id}Get a delivery (id or short code)key
Params: id
Responses: 200 OK
PATCH/v1/deliveries/{id}Update instructions / contingency / drop-off contactkey
Params: id
{
"type": "object",
"properties": {
"instructions": {
"type": "string"
},
"contingency": {
"type": "object"
},
"dropoff_contact": {
"type": "object"
},
"window_end": {
"type": "string"
},
"metadata": {
"type": "object"
}
}
}Responses: 200 OK
POST/v1/deliveries/{id}/fundFund from wallet and start dispatchkey
Params: id
Responses: 200 OK402 insufficient_funds
POST/v1/deliveries/{id}/cancelCancel (free before a driver is assigned; a cancellation fee after)key
Params: id
{
"type": "object",
"properties": {
"reason": {
"type": "string"
}
}
}Responses: 200 OK409 invalid_state (already picked up)
POST/v1/deliveries/{id}/retryRetry dispatch after no_driver_found (funds re-held at the same price; starts a fresh search and makes previously declining drivers eligible again). GET the delivery first: dispatch_summary explains why the search failed.key
Params: id
Responses: 200 OK409 invalid_state (not no_driver_found) or retry_state_changed
POST/v1/deliveries/{id}/confirmConfirm a delivered job and release payment (auto after 24h)key
Params: id
Responses: 200 OK
POST/v1/deliveries/{id}/tipTip the driver; reuse idempotency_key for retrieskey
Params: id
{
"type": "object",
"required": [
"amount_cents",
"idempotency_key"
],
"properties": {
"workspace_id": {
"type": "string",
"format": "uuid",
"description": "Country workspace owned by this account. Quotes/bookings infer it from pickup when omitted; money endpoints default to the original workspace. Never converts currency."
},
"amount_cents": {
"type": "integer"
},
"idempotency_key": {
"type": "string",
"minLength": 8,
"maxLength": 100
}
}
}Responses: 200 OK
POST/v1/deliveries/{id}/rateRate the driver 1–5key
Params: id
{
"type": "object",
"required": [
"stars"
],
"properties": {
"stars": {
"type": "integer"
},
"comment": {
"type": "string"
}
}
}Responses: 200 OK
POST/v1/deliveries/{id}/disputeOpen a dispute (funds stay held)key
Params: id
{
"type": "object",
"required": [
"reason"
],
"properties": {
"reason": {
"type": "string"
},
"evidence": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
}
}
}
}Responses: 201 OK
POST/v1/deliveries/{id}/returnCreate the reverse leg (return to pickup)key
Params: id
Responses: 201 OK
GET/v1/deliveries/{id}/messagesMessages with the driverkey
Params: id
Responses: 200 OK
POST/v1/deliveries/{id}/messagesMessage the driverkey
Params: id
{
"type": "object",
"required": [
"body"
],
"properties": {
"body": {
"type": "string"
}
}
}Responses: 201 OK
Sandbox
POST/v1/sandbox/keysMint a 24-hour sandbox (test-mode) API key — no account neededkey
Responses: 201 { success, key, mode: "test", expires_at, console_url }429 Three keys per hour per network
Status
GET/v1/status/history90 daily buckets of API traffic: requests, error_rate, p95_mskey
Responses: 200 { success, days: [{ date, requests, error_rate, p95_ms }] }
Discovery
GET/v1/intercity/lanesSame-day city-to-city routes: each launch city's neighbouring cities within 160 km by road, with distance, drive time and a sample price
Responses: 200 OK
GET/v1/service-areasList launch cities
Responses: 200 OK
GET/v1/coverageIs an address/point covered?
Params: addresslatlng
Responses: 200 OK
GET/v1/statsLive platform stats
Responses: 200 OK
Quotes
POST/v1/quotesPrice in the pickup country currency; returns required country workspace (no auth needed)
{
"type": "object",
"required": [
"pickup",
"dropoffs"
],
"properties": {
"workspace_id": {
"type": "string",
"format": "uuid",
"description": "Country workspace owned by this account. Quotes/bookings infer it from pickup when omitted; money endpoints default to the original workspace. Never converts currency."
},
"pickup": {
"type": "object",
"required": [],
"properties": {
"address": {
"type": "string",
"description": "Street address; geocoded server-side if lat/lng omitted"
},
"lat": {
"type": "number"
},
"lng": {
"type": "number"
},
"contact_name": {
"type": "string"
},
"contact_phone": {
"type": "string"
},
"instructions": {
"type": "string"
},
"window_start": {
"type": "string",
"format": "date-time"
},
"window_end": {
"type": "string",
"format": "date-time"
},
"proof_required": {
"type": "array",
"items": {
"type": "string",
"enum": [
"photo",
"signature",
"recipient_name",
"otp"
]
}
}
}
},
"dropoffs": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": {
"type": "object",
"required": [],
"properties": {
"address": {
"type": "string",
"description": "Street address; geocoded server-side if lat/lng omitted"
},
"lat": {
"type": "number"
},
"lng": {
"type": "number"
},
"contact_name": {
"type": "string"
},
"contact_phone": {
"type": "string"
},
"instructions": {
"type": "string"
},
"window_start": {
"type": "string",
"format": "date-time"
},
"window_end": {
"type": "string",
"format": "date-time"
},
"proof_required": {
"type": "array",
"items": {
"type": "string",
"enum": [
"photo",
"signature",
"recipient_name",
"otp"
]
}
}
}
}
},
"service": {
"type": "string",
"enum": [
"auto",
"local",
"intercity"
],
"default": "auto",
"description": "auto picks intercity when the drop-off is in a neighbouring launch city: same-day, one dedicated driver, up to 160 km by road. Longer trips are refused (too_far)."
},
"speed": {
"type": "string",
"enum": [
"same_day",
"express",
"standard",
"shared"
],
"default": "same_day",
Responses: 200 OK422 outside_coverage / prohibited_item
OAuth
POST/oauth/registerRegister an OAuth client (RFC 7591, no auth)
{
"type": "object",
"required": [
"redirect_uris"
],
"properties": {
"client_name": {
"type": "string"
},
"redirect_uris": {
"type": "array",
"items": {
"type": "string"
}
},
"token_endpoint_auth_method": {
"type": "string",
"enum": [
"none",
"client_secret_post"
]
}
}
}Responses: 201 OK
POST/oauth/tokenExchange an authorization code (PKCE S256) or rotate a refresh token
Responses: 200 OK
POST/oauth/revokeRevoke an access or refresh token (RFC 7009)
Responses: 200 OK
GET/v1/oauth/connectionsApps connected to this account via OAuthkey
Responses: 200 OK
DELETE/v1/oauth/connections/{id}Disconnect an app (revokes its tokens)key
Params: id
Responses: 200 OK
Webhooks
GET/v1/webhooksList webhookskey
Responses: 200 OK
POST/v1/webhooksRegister a webhook (HMAC-signed)key
{
"type": "object",
"required": [
"url"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"events": {
"type": "array",
"items": {
"type": "string"
}
}
}
}Responses: 201 OK
DELETE/v1/webhooks/{id}Remove webhookkey
Params: id
Responses: 200 OK
Shopify
GET/v1/shopify/shopsShopify shops connected to this accountkey
Responses: 200 OK
GET/v1/shopify/ordersShopify orders seen by the app with their delivery statuskey
Params: statuslimit
Responses: 200 OK
POST/v1/shopify/orders/{ref}/bookBook a delivery for a Shopify order (by order name like #1001, order id, or our row id); fetches the order from Shopify if neededkey
Params: ref
{
"type": "object",
"properties": {
"force": {
"type": "boolean",
"description": "Ignore the shop's radius / size limits"
},
"shop": {
"type": "string",
"description": "myshopify domain when several shops are connected"
}
}
}Responses: 200 Not booked — see order.status (skipped / needs_funds / failed) and order.last_error201 OK
Store connectors
GET/v1/{platform}/storesWooCommerce / BigCommerce / Wix / Squarespace stores connected to this accountkey
Params: platform
Responses: 200 OK
GET/v1/{platform}/ordersOrders the connector has seen on the platform, with their delivery statuskey
Params: platformstatuslimit
Responses: 200 OK
POST/v1/{platform}/orders/{ref}/bookBook a delivery for a store order (by order number like #1001, the platform's order id, or our row id); fetches the order from the platform if neededkey
Params: platformref
{
"type": "object",
"properties": {
"force": {
"type": "boolean",
"description": "Ignore the store's radius / size limits"
},
"store": {
"type": "string",
"description": "Store id, URL or key when several stores of that platform are connected"
}
}
}Responses: 200 Not booked — see order.status (skipped / needs_funds / failed) and order.last_error201 OK
POST/v1/{platform}/orders/{ref}/returnBook the return leg (drop-off back to the store) for a delivered store orderkey
Params: platformref
Responses: 201 OK