Go from an API key to a robot en route in about five minutes.
1Pick a language
2Try It!
Loading…
Try It! to start a request and see the response here!Everything below runs against the sandbox, https://api.sandbox.serverobotics.com. Sandbox deliveries are served by simulated robots, so you can run the full lifecycle end to end without a real courier moving.
Every v2 endpoint lives under your partner, so the base URL for all calls is https://api.sandbox.serverobotics.com/v2/partners/$PARTNER_ID. Endpoint paths in this guide and the API Reference, such as POST /quotes, are relative to that base URL. In Try It, set partner_id once in the base URL and it applies to every endpoint.
1. Make an authenticated call
Every request carries your API key in the X-API-Key header. Start with something harmless. How many robots are on duty near a location:
curl "https://api.sandbox.serverobotics.com/v2/partners/$PARTNER_ID/robots/nearby_count?lat=34.0227&lng=-118.2798" \
-H "X-API-Key: $API_KEY"{ "count": 3, "lat": 34.0227, "lng": -118.2798, "radius_m": 1000 }A 401 means the key or partner id is wrong. See Authentication for where keys come from.
2. Spin up a simulated fleet
A sandbox partner with no robots gets couriers_busy on every quote. Give yourself a fleet first, inside the sandbox service area (the examples here use Los Angeles, near USC):
curl -X POST "https://api.sandbox.serverobotics.com/v2/partners/$PARTNER_ID/simulated_fleet/power_up" \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{ "fleets": [{
"fleet_size": 3,
"robot_naming_prefix": "cor_test-",
"start_gps": { "lat": 34.0227, "lng": -118.2798 },
"randomized_starting_offset_meters": 5,
"gps_telemetry_frequency": 1000,
"job_assignment_poll_frequency": 5000,
"auto_job_event_frequency": 20000,
"max_robot_speed_mph": 5,
"auto_advance_jobs": true
}] }'auto_advance_jobs: true makes robots walk the delivery lifecycle on their own. Set it to false when you want to drive each stage yourself, as in step 5.
Fleets are capped at 10 robots across all of a partner's sandbox fleets combined. Poll GET /simulated_fleet/status until every robot reports is_ready: true.
Ready is not the same as dispatchableRobots report ready within a second or two, but the dispatcher takes about 30 seconds longer to start offering them. Quotes in that window return
503 couriers_busy. Retry the quote every few seconds until it succeeds rather than treating the firstcouriers_busyas a failure.Outside the sandbox service area, quotes return
couriers_busyno matter how long you wait, so a fleet that never becomes quotable usually means the pickup is out of area.
3. Quote a delivery
A quote prices a potential delivery and reserves nothing. It is valid for 5 minutes and can only be used once.
curl -X POST "https://api.sandbox.serverobotics.com/v2/partners/$PARTNER_ID/quotes" \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"pickup": {
"address": {
"street_address_1": "3201 S Figueroa St",
"city": "Los Angeles",
"state": "CA",
"zip_code": "90007",
"country": "US"
},
"location": {
"lat": 34.0227,
"lng": -118.2798
}
},
"dropoff": {
"address": {
"street_address_1": "3375 S Hoover St",
"city": "Los Angeles",
"state": "CA",
"zip_code": "90007",
"country": "US"
},
"location": {
"lat": 34.0226,
"lng": -118.2835
}
}
}'You get back a quote_id, a fee in cents, and ETAs. If the delivery isn't possible you get a 422 explaining why: untraversable pickup, unreachable deadline, or distance over the limit.
If Serve recognises the merchant at the pickup, the response also lists suggested_spots, which are nearby Serve parking Spots. Add the optional id and name fields to the pickup to help Serve recognise the merchant.
4. Turn the quote into a job
curl -X POST "https://api.sandbox.serverobotics.com/v2/partners/$PARTNER_ID/jobs" \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{
"quote_id": "dqt_qUdje83jhdk",
"external_order_id": "SP 937-215",
"delivery_items": [
{
"name": "Burrito",
"quantity": 1
}
],
"pickup": {
"address": {
"street_address_1": "3201 S Figueroa St",
"city": "Los Angeles",
"state": "CA",
"zip_code": "90007",
"country": "US"
},
"location": {
"lat": 34.0227,
"lng": -118.2798
}
},
"dropoff": {
"address": {
"street_address_1": "3375 S Hoover St",
"city": "Los Angeles",
"state": "CA",
"zip_code": "90007",
"country": "US"
},
"location": {
"lat": 34.0226,
"lng": -118.2835
}
}
}'Reusing a spent quote returns 409 used_quote.
To send the robot to one of the quote's suggested_spots, add "selected_spot_id": "<spot_id>" to the matching waypoint. Serve ignores an unrecognised Spot and still creates the job. Without a selection, Serve may route the robot to the nearest known Spot.
Skip the two-stepIf you always commit to whatever quote you receive,
POST /jobs/instantquotes and creates in one call. Send anIdempotency-Keyheader and retries are safe, because the same key replays the original response rather than creating a second job.One wrinkle worth designing for: rejections replay too. Retrying a transient
couriers_busyunder the same key returns that same rejection. Send a new key when you genuinely want another attempt.
5. Watch it happen
Poll GET /jobs/{job_id} and read status as it moves through pending_courier_assignment → pickup_en_route → pickup_arrived → dropoff_en_route → dropoff_arrived → dropoff_complete.
To skip polling, receive the same updates as they happen. Connect to the partner WebSocket at wss://api.sandbox.serverobotics.com/v2/ws/partners/$PARTNER_ID, or set a webhook_url on your partner account. The WebSocket authenticates with X-API-Key, or an api_key query parameter where headers aren't available. Both channels deliver the same EventEnvelope, carrying event.delivery_status and event.courier_status events.
With auto_advance_jobs: false, you drive it instead:
curl -X POST "https://api.sandbox.serverobotics.com/v2/partners/$PARTNER_ID/simulated_fleet/jobs/$JOB_ID/advance" \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{ "target_stage": "dropoff_arrived" }'That's the whole loop. To draw the planned route on a map, call GET /jobs/{job_id}/route. Each leg's route is a GeoJSON FeatureCollection of sidewalk segments, in order. Each feature is a LineString with routing properties such as time_estimate and next_maneuvers. Join the features' coordinates to get one path. The route is only available while the job is active. Once the job completes, the endpoint returns 404 route_not_found.
Testing the unhappy paths
Sandbox lets you force failures rather than waiting to stumble into them. Pass simulate_rejection on a quote or instant job:
pickup_untraversable · dropoff_untraversable · delivery_distance_exceeds_limit · outside_operating_hours · pickup_deadline_unreachable · dropoff_deadline_unreachable · couriers_busy · internal_error
Build against these early. couriers_busy in particular is a normal transient condition in production, not an exceptional one.
Limits and conventions
The rate limit is 600 requests per minute, and a 429 carries a retry-after header in seconds. Dates are UTC in ISO 8601, 2020-05-15T15:50:38Z. Coordinates are decimal degrees in WGS84. Fees are integer cents.