Routerra LogoTeams
Browse articles· Response objects & enums
For ownersUpdated 2026-09-08 · 10 min read

Response objects & enums

Field reference for the Routerra Teams API — stop, route and change objects, every enum value they can hold, what the API deliberately doesn't do, and what changed.

The field reference for everything the external API hands back. Two conventions apply everywhere: fields that are null are omitted rather than sent as null, and units differ per field — metres, minutes and seconds all appear, so check Dates, times and units before you multiply anything.

Stop

The ExternalStopDto. A stop carries what you sent, what optimization worked out, and what the driver later did with it — grouped here by which of those three it is.

GroupFields
Identityid name address latitude longitude addressBookEntryId metadata
SettingsserviceTime (seconds) timeWindows[] priority stopSide load assignedDriverId note
Recipientemail phone (E.164) recipientName
Optimization resultposition expectedArrival expectedArrivalLocal driveTime (minutes) driveDistance (metres) waitTime (minutes) errorType
Deliverystatus statusChangedAt statusChangedAtLocal failureReason

metadata is an opaque blob: whatever JSON you send on a stop comes back unchanged, nested objects and arrays included. Routerra never reads it — which also means you cannot search on it.

Route

The ExternalRouteDto. One route is one driver's day.

GroupFields
Routeid driverId driverName status timezone distance (metres) time (minutes) stopsCount startLocation finishLocation
ProgressdeliveredCount failedCount pendingCount startedAt completedAt nextStop with id, name, position, expectedArrival, expectedArrivalLocal
Detailstops[] — only under view=detailed

nextStop is the cheapest way to follow a driver's progress without asking for the whole stop list on every poll.

Change

The ExternalChangeDto — one entry in a plan's change journal, whether it was proposed and is waiting, or was applied outright.

GroupFields
What and whereid planId planName operation status source
Who and whenproposedBy with type, id, name · appliedBy createdAt proposedAt appliedAt rejectedAt expiresAt
Contentsdiff (an ExternalActionPreviewDto) stale howToApply deduplicated

How proposals are created and applied is covered in Proposed changes.

Enumerations

TypeValues
PlanStatusCREATED DISPATCHING_IN_PROGRESS DISPATCHED DISPATCH_FAILED HAS_CHANGES APPROVED CANCELLED. The file-import statuses (IMPORT_*) only ever appear on plans created from the dashboard
RouteStatusOPTIMIZED IN_PROGRESS COMPLETED
RouteStopStatusPENDING DELIVERED NOT_DELIVERED
RouteStopFailureReasonCUSTOMER_NOT_AVAILABLE WRONG_ADDRESS CUSTOMER_REFUSED BUSINESS_CLOSED ACCESS_RESTRICTED OTHER
RouteStopErrorTypeGEOCODE_FAILED CANT_VISIT_TIME_WINDOW CAPACITY_EXCEEDED OUTSIDE_TRANSIT_AREA MAX_JOBS_CONSTRAINT NO_DRIVER_WITH_ZONE_ACCESS ALLOWED_VEHICLE_CONSTRAINT
DispatchStrategyBALANCED_MIX BALANCE_BY_STOPS BALANCE_BY_TIME FASTEST_OVERALL
StopPriorityEARLIEST AUTO LATEST
StopSideLEFT RIGHT ANY
VehicleTypeCAR VAN TRUCK BIKE SCOOTER
ApiKeyScopeREAD WRITE DISPATCH
PlanChangeOperationProposable: DISPATCH APPROVE REVOKE CANCEL. Journal-only: OPTIMIZE ADD_STOPS UPDATE_STOP REMOVE_STOPS DUPLICATE
PlanChangeStatusAPPLYING PROPOSED APPLIED REJECTED EXPIRED FAILED. Every row is written APPLYING before its operation runs, so an in-flight change reads APPLYING until it settles — on a DISPATCH that lasts the whole optimization. UNDONE exists in the enum but is reserved and never written
WebhookEventTypeplan.approved route.started route.completed stop.delivered stop.failed

PlanStatus is a state machine, not a flat list — which transitions are legal is in Plan lifecycle & statuses.

What the API doesn't do

Listed plainly so you don't design an integration around a capability that isn't there. Some of these are covered by the dashboard; some are in progress.

LimitationWhat to do instead
No lookup of a stop by your own identifier. metadata comes back as you sent it, but you can't query on it, and there is no GET /stops/{stopId}Keep your own orderId → (planId, stopId) mapping. stopId arrives in the response to …/stops and in the plan.approved and route.started webhooks
Webhooks can't be configured over the API, and a failed delivery is not retriedRegister your endpoint in the dashboard, and for anything critical run a periodic reconciliation against GET /plans/{planId}
stop.delivered and stop.failed don't carry metadataMatch on the stopId from a snapshot you already hold
The address book can't be managed over the API — no create, update or delete. Entries are still created automatically from the stops you sendEdit and tidy the book in the dashboard; from an integration, send addresses on the stops themselves
Drivers can't be created or deleted — only GET and a PATCH of operational fieldsOnboard drivers in the dashboard
Depots and zones are read-only, inside GET /teamManage them in the dashboard
Proof of delivery — photos and signatures — is not exposedThe delivery status and the driver's note are available over the API; the files themselves live in the dashboard and the export
The public tracking link is not returned by any API objectRouterra sends the customer notification carrying that link itself — fill in the stop's email and phone
Live driver coordinates are not availableFollow progress through the route.* webhooks and nextStop in GET /plans/{planId}
There is no pagination anywhere — only limit (plans 100, address book 50, changes 100)Narrow the date range and walk day by day
A cancelled plan can't be restored over the API, even though it can be cancelledRestore it in the dashboard, or duplicate the plan with …/duplicate
A proposal can't be rejected over the API, and an applied change can't be undoneReject in the dashboard; roll back with ordinary revoke, approve or plan cancellation

What's new

2026-09-07

  • Key scopes READ, WRITE and DISPATCH, with the new 403 insufficient_scope. See API authentication & scopes.
  • Soft plan cancellation: the CANCELLED status, status and restorable in the response, the 409 plan_cancellation_blocked error, and cancelled plans dropping out of GET /plans.
  • A dashboard reserve inside the daily quota: the optimizationQuota.availableForApi field.
  • Idempotency: clientRequestId on …/dispatch, …/stops, …/stops/{stopId} and …/stops/remove, the deduplicated flag in their responses, and the client_request_id_conflict and client_request_in_progress errors.
  • A new endpoint, POST …/preview — see what an operation would do without changing anything.
  • Proposals: POST …/changes, GET /changes, GET /changes/{changeId} and POST /changes/{changeId}/apply; over MCP, a propose parameter and an automatic hand-off to a dispatcher for a key without dispatch.
  • Recipient contacts email, phone (E.164) and recipientName — on stops, in the stop merge-patch, and in the address book.
  • PATCH /drivers/{driverId} now refuses shift and vehicle changes while the driver has a route under way: 409 driver_route_in_progress.
  • MCP: 18 toolsapply_change and list_changes added; approve_plan moved to the {applied, result, preview, change} envelope; autoRevoke removed from dispatch_plan (it stays in REST).
  • Webhooks documented — see Webhooks. The mechanism itself predates this release and did not change.
  • The limitations section above was added.

Everything is additive except the three items in the box and the approve_plan envelope change on MCP. All 18 MCP tool names are frozen: nothing was renamed or removed.

Fixes shipped with the same release

  • metadata on a stop works again. Any request carrying that field — POST /optimize, POST …/stops, PUT …/stops/{stopId}, and the same tools over MCP — used to answer 500. The blob is now accepted and returned as sent, nested objects and arrays included.
  • A client mistake no longer looks like a server failure. The wrong method on an existing path answers 405 method_not_allowed with an Allow header, and the wrong Content-Type answers 415 unsupported_media_type. Both used to be a 500.
  • GET /changes validates limit. Values outside 1–100 are refused with 400 validation_error, as on every other list. Anything used to be accepted.
  • Concurrent requests no longer collide in the address book. Adding stops in parallel under the same new name could drop one request into a 500 and leave the plan with no stops.

Corrections to this reference

These describe behaviour that never changed — earlier drafts of this documentation had it wrong. The first one may mean you are doing work you do not need to; the two after it change how an error handler should branch. All are worth a look even if nothing in your integration moved.

  • The API geocodes addresses, and always has. The retired optimize-api article said the opposite — that latitude and longitude were required, that a stop carrying only an address was rejected with 400 empty_coordinates, and that you had to geocode on your side before calling. None of that was true of the implementation. Send an address and the server resolves it; a resolution failure does not even fail the request, it comes back per-stop in geocodeFailedStops. If you built a geocoding step to satisfy the old article you can retire it, and 400 empty_coordinates is not a case an external caller needs to handle — see How a stop is located.
  • concurrent_optimization_limit arrives as 429, not 409. The implementation always returned 429. A handler that looks for this code in its 409 branch will never find it.
  • An MCP call that starts an optimization spends two rate-limit tokens, not one. The URI-based filter puts /mcp in the general tier, but optimize_plan, dispatch_plan and applying a DISPATCH proposal each take an optimize-tier token by hand on top of it. When that hand-taken token is gone the answer is 429 too_many_requests, not rate_limit_exceeded — see API limits & quotas.
  • 400 bad_request is documented. It always existed; today it comes from an unknown ?view= value. Its overlap with validation_error is called out explicitly in API error reference.
  • Checking the age of the t value in a webhook signature is entirely the receiver's job. Routerra is the sender and does not perform that check. See Webhooks.

Other clarifications in this pass: the rate-limit 429 carries rate_limit_exceeded, not too_many_requests; a completed route blocks cancellation just as a running one does; a team with no subscription record is never given a 402; the request limits are token buckets refilling continuously rather than fixed windows; and empty members of a response envelope are omitted rather than sent empty.

What's next

Was this article helpful?

Still stuck?

Write to us — a human reads every message.

Contact support