Skip to content
REST API v1 · MCP

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.

Base URLhttps://api.rentadriver.ai/v1
MCPclaude mcp add --transport http rentadriver https://mcp.rentadriver.ai/mcp
Connect an agentSet up MCP in your preferred client.Make your first callGet a quote without an API key.Try the sandboxTest the flow with simulated drivers.
JSON requestsMoney in minor unitsISO-8601 UTC timestampsGlossary ↗OpenAPI JSON ↗

Route-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.

Load is assessed before routing

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.

Vehicle matching

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.

Courier limits

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.

Is this quote traffic-aware?

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, caching and ETAs

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.

  1. 1
    Call GET /v1/workspaces to see balances and available markets. An admin key can enable a country with POST /v1/workspaces and {"country_code":"GB"}.
  2. 2
    Get a quote. Its workspace identifies the pickup country, currency, and whether that workspace is enabled. Public quotes do not create a workspace.
  3. 3
    Fund the matching wallet using POST /v1/wallet/deposit with workspace_id. USDC deposits work only for USD workspaces.
  4. 4
    Create the delivery with its quote ID. The pickup determines its workspace; optional workspace_id checks your intent. Holds, refunds, retries, tips, and driver earnings keep that currency.
Selecting a workspace

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.

Caps, sandbox and agents

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.

In the console

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.

From the API

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

StatusMeaningMoney
quotedCreated, not funded—
fundedScheduled; dispatch starts 45 min before the windowheld
dispatchingOffering to on-shift drivers in expanding radius wavesheld
assignedA driver acceptedheld
en_route_pickup → at_pickup → picked_upDriver steps with pickup photoheld
en_route_dropoff → at_dropoff → deliveredDrop-off proof capturedheld
confirmed / paidYou confirmed (or 24h passed); driver is paidcaptured
no_driver_foundSix waves, no acceptancereleased
cancelledYou cancelled. Free before assignment; a cancellation fee afterreleased (minus fee)
failedDriver could not complete; a review is openedheld until resolved
disputedYou opened a disputeheld until resolved
returnedItem brought back to pickupcaptured

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_multiplier

The 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

AdjustmentHow it worksWhere to inspect it
Route and trafficMotor-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 hoursA 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 demandSurge 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 vehicleCity-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 floorDriver 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.

Same day

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.

How the price is built

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.

Money

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.

Cities and tools

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.

photosignaturerecipient_nameotp
At pickup

photo. Default: photo.

At drop-off

photo, signature, recipient_name or otp. Default: photo + recipient name. Recipient identity checks are not yet supported.

OTP handoff

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.

Signature

Each POST carries X-RentADriver-Event and X-RentADriver-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, t + "." + rawBody).

Retries

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

delivery.createdfundeddispatchingassigneden_route_pickupat_pickuppicked_upen_route_dropoffat_dropoffdeliveredconfirmedpaidno_driver_foundcancelledfaileddisputedmessagelocation

Shopify

Merchants install the RentADriver app for Shopify from Shopify admin (or from /shopify).

What the app does

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.

Agents on the same account

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

HTTPcodeWhat to do
400validation_error / bad_requestFix the payload; details lists the paths
401unauthorized / invalid_api_keySend a valid x-api-key
402insufficient_fundsDeposit (card checkout URL or x402), then POST /fund
403spending_capRaise the cap in account controls
409invalid_state / conflict / already_takenRead status and next_action; do not retry blindly
410quote_expired / offer_expiredRequest a new quote
422outside_coverage / prohibited_item / proof_requiredChange the request; see details
429rate_limitedBack off; headers include X-RateLimit-Remaining

Prohibited items

AlcoholTobacco and vapesCannabisWeapons and ammunitionExplosives and fireworksControlled or prescription-only medicines without a licensed senderHazardous or flammable goodsLoose lithium batteriesLive animalsHuman remainsCash above the city capAnything illegal to transport

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 Code
claude mcp add rentadriver -- npx -y rentadriver-mcp
Codex CLI
codex mcp add rentadriver -- npx -y rentadriver-mcp
Gemini CLI
gemini mcp add rentadriver npx -y rentadriver-mcp
VS Code
code --add-mcp '{"name":"rentadriver","command":"npx","args":["-y","rentadriver-mcp"]}'
Other clients

Cursor, Windsurf, Claude Desktop and every other client: see the per-client commands.

Resources and prompt

delivery://, coverage://, openapi://spec and the plan_delivery prompt ship with the server.

Remote transport

https://mcp.rentadriver.ai/mcp with an x-api-key header, or OAuth from hosts that support it.

Full MCP tools referenceA description, inputs, returns and auth for each tool.

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

Support