API limits & quotas
Rate limits, concurrent optimizations, the daily optimization quota and its dashboard reserve — every way a Routerra Teams API call gets refused with a 429.
Six limits stand between your integration and the solver, and they are independent of each other: clearing one says nothing about the next. Five of the six refuse with 429, which is the trap — tell them apart by the code field, not by the status, because the right thing to do afterwards is different in every case.
| Limit | Default | Applies to | Refusal |
|---|---|---|---|
| General rate limit | capacity 60, refilled continuously | Every endpoint and every MCP tool | 429 rate_limit_exceeded + Retry-After |
| Optimize tier | capacity 5, refilled continuously | Every optimization, on both transports: the REST paths POST /optimize — including dispatch=false — and POST …/dispatch, plus the MCP tools optimize_plan and dispatch_plan, plus POST /changes/{changeId}/apply for a DISPATCH proposal | 429 rate_limit_exceeded over REST; 429 too_many_requests wherever the token is taken by hand |
| Concurrent optimizations | 3 per team | Any dispatch, including the ones started from the dashboard | 429 concurrent_optimization_limit |
| Daily optimization quota | From the team's plan (optimizationQuota.capacity) | POST /optimize with dispatch=true, and POST …/dispatch | 429 too_many_requests; the token is handed back if the optimization fails |
| Stops per plan | The team's stop limit — 200 unless changed | /optimize, …/stops, …/dispatch | 409 stop_limit_exceeded. …/stops and …/stops/remove also cap the body itself at 200 entries, which is a 400 validation_error and applies whatever the team setting says; /optimize has no such body cap |
| Open proposals | 25 per plan, 100 per team | POST …/changes, and the MCP hand-off | 429 too_many_proposals |
The dashboard's reserve in the daily quota
| Field | Meaning |
|---|---|
capacity | The team's full daily quota |
available | How much is left in total — dashboard and API together |
availableForApi | How much of that API keys may spend. Always ≤ available |
"optimizationQuota": { "capacity": 34, "available": 26, "availableForApi": 14 }
You never need a separate call to find out where you stand: the current quota comes back from GET /team and from every POST …/dispatch response.
What to do with each refusal
rate_limit_exceeded— wait out theRetry-Afterheader and retry the same call. It is safe: nothing was applied.concurrent_optimization_limit— three optimizations are already running for the team. There is nothing to do but wait for one of them to finish and retry. You cannot free a slot by cancelling: a plan inDISPATCHING_IN_PROGRESSrefusesDELETEwith409 plan_state_transitionuntil it reachesDISPATCHED.too_many_requests— the day's optimizations are gone, or the API's share of them is: retrying sooner will not help, so retry tomorrow or ask a dispatcher to run it from the dashboard. The same code also comes back when the optimize-tier bucket is empty on a call that takes its token by hand (optimize_plan,dispatch_plan,apply_change) — that one is worth retrying in a few seconds. The message tells the two apart.too_many_proposals— the queue is full. A dispatcher has to apply or reject some of the open ones first; see Proposed changes.stop_limit_exceeded— a409, not a429. Split the work across plans.
When you retry a call that mutates something, send the original clientRequestId so it can only be applied once — see Idempotent API requests. The full list of codes is in API error reference.