Optimize a route from plain addresses
Turn a spreadsheet of address strings into coordinates, then into an optimized route — the two calls in the right order, and when one call is enough.
What you have is a column of addresses from a CRM or a spreadsheet. What the optimizer needs is coordinates. You can let the optimization geocode them for you (send the strings with `geocoding.region`), but geocoding first lets you review ambiguous matches before anything is routed.
The prompt is a complete brief — the task, this request body, the rules the API validates against, and the result shape to check against. Paste it into Claude Code, Cursor or any coding agent. The MCP setup connects an agent to the live API instead, so it can run the optimization itself.
What this shows
- —One call: send addresses without
lat/lngplusgeocoding.regionand the optimization geocodes them, refusing with 422 GEOCODING_UNRESOLVED (and the candidates) rather than guessing. - —One
POST /api/v1/geocoding/bulkcall geocodes the whole list — far cheaper than one call per address. - —One result per distinct address, in the order first sent — match by
original, never by position; astatusofambiguousornot_foundneeds a better input, and onlyprecision: "address"is exact enough to drive to. - —Geocoding is charged per distinct address, priced through at what it costs us.
- —Feed the returned coordinates straight into the optimization body.
Step 1 — geocode the addresses
Send the raw strings and get one result per distinct address back, keyed by `original`. Keep the ones with status `ok`.
curl -X POST https://r4m.ai/api/v1/geocoding/bulk \
-H "Authorization: Bearer acc_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"addresses": [
"25 Kent Ave, Brooklyn, NY 11249",
"1 Front St, Brooklyn, NY 11201",
"445 Albee Square W, Brooklyn, NY 11201"
]
}'Step 2 — optimize
You send
curl -X POST https://r4m.ai/api/v1/optimizations \
-H "Authorization: Bearer acc_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"addresses": [
{
"address": "Depot — 25 Kent Ave, Brooklyn, NY",
"lat": 40.7218,
"lng": -73.9615,
"isDepot": true,
"serviceTimeSeconds": 900
},
{
"address": "1155 Manhattan Ave, Brooklyn, NY",
"lat": 40.7304,
"lng": -73.954,
"serviceTimeSeconds": 600,
"timeWindowStart": "09:00",
"timeWindowEnd": "10:30",
"priority": 1
},
{
"address": "238 Bedford Ave, Brooklyn, NY",
"lat": 40.7135,
"lng": -73.967,
"serviceTimeSeconds": 900
},
{
"address": "1 Front St, Brooklyn, NY",
"lat": 40.7033,
"lng": -73.9881,
"serviceTimeSeconds": 600,
"priority": 2
},
{
"address": "445 Albee Square W, Brooklyn, NY",
"lat": 40.6926,
"lng": -73.9873,
"serviceTimeSeconds": 1200
}
],
"parameters": {
"travelMode": "driving",
"optimizeMode": "Time",
"roundTrip": true,
"routeStartAt": "2026-04-14T08:00",
"timezone": "America/New_York"
},
"webhookUrl": "https://your-server.com/webhooks/r4m"
}'
# 202 Accepted — this call returns immediately; the solver keeps working (2-60s)
# { "success": true, "data": { "id": "550e8400-…", "status": "pending" } }
#
# That id is how you collect the finished plan. Either:
# webhook — we POST it to your webhookUrl the moment it is ready, or
# polling — GET /api/v1/optimizations/{id} until status is "completed"You get back
The POST answers immediately with a pending id; this is the completed optimization, abridged. Collect it by polling or webhook.
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"priceMicros": 10000,
"webhookUrl": "https://your-server.com/webhooks/r4m",
"webhookStatus": "delivered",
"result": {
"routes": [
{
"routeStartAt": "2026-04-14T08:00:00-04:00",
"totalDistanceMeters": 19473,
"totalTimeSeconds": 8285,
"polyline": "ulpwFpplbMl@qA@CLOYYIIIK…",
"waypoints": [
{
"index": 0,
"arrivalAt": "2026-04-14T08:00:00-04:00",
"departureAt": "2026-04-14T08:15:00-04:00",
"waitTimeSeconds": 0,
"cumulativeDistanceMeters": 0,
"cumulativeTimeSeconds": 0,
"polylineToNext": "ulpwFpplbMl@qA@CLOYYIIIK…"
},
{
"index": 1,
"arrivalAt": "2026-04-14T09:00:00-04:00",
"departureAt": "2026-04-14T09:10:00-04:00",
"waitTimeSeconds": 2543,
"cumulativeDistanceMeters": 1416,
"cumulativeTimeSeconds": 3600,
"polylineToNext": "}brwFhckbMI{A?IGkAIqBMkC…"
}
]
}
],
"unroutedStops": []
},
"createdAt": "2026-04-14T11:58:12Z",
"updatedAt": "2026-04-14T11:58:41Z"
}
}