Routerra LogoTeams
Browse articles· Optimize API reference
For ownersUpdated 2026-07-24 · 11 min read

Optimize API reference

The full request/response reference for the Routerra Teams Optimize API — authenticate with an API key, send stops, poll for optimized routes.

The Optimize API lets your own systems run the same optimization engine the web app uses: send stops and a driver selection, get back optimized routes. It's three endpoints — one to submit work, one to poll for the result, one to look up your drivers.

All requests go to the production base URL:

https://teams-api.routerra.io/api/v1

Authentication

Every request needs an API key in the X-API-Key header. Keys start with rtk_ and are created under Settings → Developer → API keys — see API keys for the create/revoke flow. A key is scoped to the team that created it: every plan, stop, and driver you touch through the API belongs to that team.

curl "https://teams-api.routerra.io/api/v1/external/drivers" \
  -H "X-API-Key: rtk_your_key_here"

A missing header fails with 401 {"code": "invalid_api_key", "message": "Missing X-API-Key header"}; a wrong, disabled, or expired key fails with the same code and "Invalid, disabled, or expired API key."

The endpoints

MethodPathWhat it does
POST/external/optimizeCreate a plan from your stops and start optimization
GET/external/plans/{planId}Poll optimization status and fetch the routes
GET/external/driversList your team's drivers and their IDs

POST /external/optimize

One call does the whole job: it creates a plan, adds your stops to it, and starts optimization across the drivers you selected. Optimization runs asynchronously — the response comes back immediately with a planId you poll.

curl -X POST "https://teams-api.routerra.io/api/v1/external/optimize" \
  -H "X-API-Key: rtk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Tuesday deliveries",
    "date": "2026-07-28",
    "stops": [
      {
        "name": "Customer A",
        "address": "123 Main St, Chicago, IL",
        "latitude": 41.8781,
        "longitude": -87.6298
      },
      {
        "name": "Customer B",
        "address": "456 Oak Ave, Chicago, IL",
        "latitude": 41.92,
        "longitude": -87.65,
        "serviceTime": 600,
        "arrivalRangeFrom": "09:00",
        "arrivalRangeTo": "12:00",
        "metadata": { "orderId": "ORD-123" }
      }
    ],
    "driverIds": [101, 102],
    "strategy": "BALANCED_MIX"
  }'

Request fields

FieldTypeRequiredDefaultDescription
namestringno"API Plan {date}"Plan name, max 255 characters
datestringnotodayPlan date, yyyy-MM-dd
stopsStop[]yesThe stops to optimize, at least 1
driverIdsnumber[]noall team driversWhich drivers to route — IDs from GET /external/drivers
strategystringnoBALANCED_MIXDistribution strategy, see below

Stop fields

FieldTypeRequiredDescription
namestringyesStop label, max 255 characters
latitudenumberyesWGS84 latitude, decimal degrees
longitudenumberyesWGS84 longitude, decimal degrees
addressstringnoHuman-readable address — stored and shown to the driver; routing uses the coordinates
serviceTimenumbernoTime the driver spends at the stop, in seconds. Omit to use your team's default service time
arrivalRangeFromstringnoEarliest allowed arrival, HH:mm
arrivalRangeTostringnoLatest allowed arrival, HH:mm
prioritystringnoArrival priority: AUTO, EARLIEST, or LATEST — bias toward serving the stop early or late (defaults to AUTO)
stopSidestringnoCurbside parking: ANY, LEFT, or RIGHT (defaults to ANY)
notestringnoFree-text note for the driver
loadnumbernoCapacity units this stop consumes, weighed against each driver's vehicle capacity
metadataobjectnoArbitrary JSON stored with the stop and returned as-is — use it to carry your own order or customer IDs

Distribution strategies

The same four strategies as the Assign drivers & optimize dialog — see Optimize routes for when to pick which:

API valueIn the app
BALANCE_BY_STOPSBalance by Stops
BALANCE_BY_TIMEBalance by Time
FASTEST_OVERALLFastest Overall
BALANCED_MIXBalanced Mix

Response — 202 Accepted

{
  "planId": 4567,
  "status": "DISPATCHING_IN_PROGRESS",
  "stopsCount": 2,
  "createdAt": "2026-07-28T08:30:00Z"
}
FieldTypeDescription
planIdnumberThe plan's ID — use it to poll GET /external/plans/{planId}
statusstringAlways DISPATCHING_IN_PROGRESS on submit
stopsCountnumberHow many stops were accepted
createdAtstringPlan creation time, ISO 8601

GET /external/plans/{planId}

Poll this endpoint until status leaves DISPATCHING_IN_PROGRESS. While optimization is running you get the plan header only; once it finishes, the response includes every route with its stops in visit order.

curl "https://teams-api.routerra.io/api/v1/external/plans/4567" \
  -H "X-API-Key: rtk_your_key_here"

Fields that are null are omitted from the response entirely.

Plan fields

FieldTypeDescription
idnumberPlan ID
namestringPlan name
datestringPlan date, yyyy-MM-dd
statusstringSee statuses below
stopsCountnumberTotal stops in the plan
routesCountnumberNumber of routes built — present after optimization
totalTimeMinutesnumberSum of all route times, in minutes — present after optimization
totalDistanceMetersnumberSum of all route distances, in meters — present after optimization
createdAtstringISO 8601
updatedAtstringISO 8601
routesRoute[]The optimized routes — present only when status is DISPATCHED or DISPATCH_FAILED
unassignedStopsStop[]Stops the optimizer couldn't place — present only when there are any

Plan statuses

StatusMeaning
DISPATCHING_IN_PROGRESSOptimization is running — keep polling
DISPATCHEDDone. routes holds the result; check unassignedStops for anything left out
DISPATCH_FAILEDOptimization failed. Stops in unassignedStops carry an errorType explaining why

Route fields

FieldTypeDescription
idnumberRoute ID
driverIdnumberAssigned driver
driverNamestringAssigned driver's name
distancenumberRoute distance, in meters
timenumberRoute duration, in minutes
stopsCountnumberStops on this route
startLocationLocationWhere the route starts
finishLocationLocationWhere the route ends — omitted if the driver has no finish location
stopsStop[]The stops in optimized visit order

A Location is { "address", "latitude", "longitude" }.

Stop fields (response)

FieldTypeDescription
idnumberStop ID
namestringStop label
addressstringAddress as submitted
latitude, longitudenumberCoordinates as submitted
positionnumberVisit order within the route, starting at 1
expectedArrivalstringPredicted arrival, ISO 8601 with the route's local UTC offset
driveTimenumberDrive time from the previous point, in minutes
driveDistancenumberDrive distance from the previous point, in meters
waitTimenumberIdle time before an arrival window opens, in minutes — omitted when under 5 minutes
serviceTimenumberService time in seconds — omitted when the team default applies
statusstringDelivery status: PENDING, DELIVERED, or NOT_DELIVERED
errorTypestringWhy the stop couldn't be placed — only on unassigned stops
metadataobjectYour metadata, returned exactly as submitted

Stop error types

When the optimizer can't fit a stop, it lands in unassignedStops with one of:

errorTypeMeaning
CANT_VISIT_TIME_WINDOWNo driver can reach the stop inside its arrival window
CAPACITY_EXCEEDEDThe stop's load doesn't fit any driver's vehicle capacity
NO_DRIVER_WITH_ZONE_ACCESSThe stop sits in a zone none of the selected drivers may enter
MAX_JOBS_CONSTRAINTEvery driver already carries their maximum number of stops
ALLOWED_VEHICLE_CONSTRAINTThe stop's zone restricts vehicle types and no allowed vehicle was selected
OUTSIDE_TRANSIT_AREAThe coordinates fall outside the routable road network
GEOCODE_FAILEDThe stop has no usable coordinates — seen on stops imported from files, not on API-created stops

GET /external/drivers

Lists all drivers in your team, with the IDs you pass as driverIds when optimizing. Drivers themselves are managed in the web app — see Manage drivers.

curl "https://teams-api.routerra.io/api/v1/external/drivers" \
  -H "X-API-Key: rtk_your_key_here"
[
  {
    "id": 101,
    "name": "John Smith",
    "vehicleType": "VAN",
    "vehicleCapacity": 100,
    "maxStops": 30,
    "shiftStartTime": "08:00",
    "shiftEndTime": "17:00",
    "startLocation": {
      "address": "456 Depot Rd, Chicago, IL",
      "latitude": 41.85,
      "longitude": -87.65
    },
    "liveRoadData": true,
    "avoidTolls": false,
    "avoidHighways": false
  }
]
FieldTypeDescription
idnumberDriver ID — use in driverIds
namestringDriver name
vehicleTypestringCAR, TRUCK, VAN, BIKE, or SCOOTER
vehicleCapacitynumberCapacity in load units
maxStopsnumberMaximum stops per route
shiftStartTime, shiftEndTimestringWorking hours, HH:mm
startLocationLocationWhere routes start
finishLocationLocationWhere routes end — omitted if not set
liveRoadDatabooleanWhether routing uses live traffic
avoidTollsbooleanRouting avoids toll roads
avoidHighwaysbooleanRouting avoids highways

As everywhere in the API, null fields are omitted — a driver without a shift simply has no shiftStartTime.

Errors

Every error is JSON with a code and a human-readable message:

{ "code": "validation_error", "message": "stops[0].name: must not be blank" }

Validation failures list each offending field in the message, separated by semicolons.

HTTPcodeWhen
400validation_errorA request field fails validation (blank stop name, empty stops, …)
400empty_coordinatesA stop is missing latitude/longitude
400malformed_requestThe body isn't valid JSON for this endpoint
400invalid_driver_selectionA driverIds entry doesn't exist or belongs to another team
401invalid_api_keyMissing, wrong, disabled, or expired key
402subscription_requiredThe team has no active subscription
404plan_not_foundThe plan doesn't exist or belongs to another team
409stop_limit_exceededThe plan would exceed your team's stops-per-plan limit
429too_many_requestsOptimization quota exhausted — see limits below
500internal_errorSomething unexpected — retry, and contact support if it persists

Limits

Two per-team limits apply, both checked before any work starts:

  • Optimization runs per day — each successful POST /external/optimize consumes one run from the same daily quota the web app uses (default 30 per day). A run whose optimization fails is refunded. Exceeding it returns 429 too_many_requests.
  • Stops per plan — one plan holds at most your team's stop limit (default 200 stops). Exceeding it returns 409 stop_limit_exceeded.

What's next

  • API keys — create and manage the keys this API authenticates with
  • Webhooks — get pushed a signed event when routes start, complete, or fail instead of polling
  • Optimize routes — what the optimizer respects, in product terms

Was this article helpful?

Still stuck?

Write to us — a human reads every message.

Contact support