Optimiciti API Specification
Optimiciti API
Optimiciti turns a real-world routing problem — vehicles, stops, time windows, capacities, and costs — into an optimized plan: who goes where, in what order, and at what time. This API gives you direct, programmatic access to the same solvers that power the Optimiciti app. It's a good fit for scheduling deliveries, field service, home visits, or any fleet that has to visit many places efficiently.
Beta. Endpoints and fields may still change. Using the API requires accepting our terms of service.
How it works
A real optimization can take longer than a single request should wait, so solving is asynchronous — you submit, then poll:
- Get travel costs (choose one). Every solve needs the cost of travelling between your locations, and
POST /problemdoes not compute them for you. Either (a) callPOST /cost_matrixand pass itstravelCostResponsesasproblemData.travelCosts— it computes them from coordinates by default (autoCalculate*defaults totrue,geomethod, no road-network call) — or (b) supply your owntravelCosts. (There is nocostSettingson/problemitself; auto-calculation lives on/cost_matrix.) An all-zero matrix is fine — it solves with every stop assigned. The trap is a null cost or duration on a pair: the solver reads either as unreachable. A matrix with no usable pair at all is rejected up front with400 UNCOSTED_TRAVEL_MATRIX, and an absent or empty one withMISSING_TRAVEL_COSTS. A matrix that is only partly filled is accepted, and the runsucceededwith the stops behind the empty pairs left unassigned and no error. So read how many stops were assigned, not only the status. - Submit the problem.
POST /problemwith your locations, vehicles, and constraints. You get an id back right away (the response calls itrun_id); the solver runs in the background. - Poll for status.
GET /problem/{problemId}— the same id — orPOST /run_status, until the run reaches a terminal state:succeeded,failed, orcancelled. Poll every 2 to 5 seconds: a tight loop gains nothing and will run into the gateway's rate limit (429). A status response stays small: it carriesstatusand anyerror— never the routes (aprogresskey is present but alwaysnull). - Fetch the results. Once the run is
succeeded, get the optimized routes withPOST /run_results_by_run_id. Results are a separate call on purpose — route payloads are large, and polling is frequent.
(problemId in the path and run_id in responses are the same identifier — the two names refer to one run.)
Your runs stay available afterwards: list them, re-open their results, and rename or delete them at any time — see the History section. Saving and editing scenarios is part of the Optimiciti web app, not of this API.
Authentication
There are two ways to authenticate, and which one you use depends on where the request comes from. For programmatic access — scripts, servers, SDKs, anything without a browser — use an API key.
Send the key as a bearer credential in the Authorization header on every request:
Code
For example:
Code
Your API key, not this field, decides whose runs are returned.
An API key replaces the Clerk token for server-to-server calls — you do not send both. The gateway resolves the key to your account and derives your identity from it, so, exactly as with a token, your identity is taken from the credential and never from a userId in the request body. During the beta, keys are issued by us and are not self-service yet. To request one, email [email protected]. Once you have a key, the header above is all you need: there is nothing to activate.
The browser app authenticates differently: it sends a Clerk-issued JSON Web Token (Authorization: Bearer <token>), verified on each call for signature, expiry, and issuer. You only need this if you are calling the API from a signed-in Optimiciti web session; direct API integrations should use an API key.
One thing that trips people up: api.optimiciti.com sits behind a gateway, and a request that arrives with no Origin header (a curl call, a script, or any server-to-server request) must carry an API key. Without one it gets a 401 on authenticated tiers — that is expected, not a broken credential.
Requests and responses
Every response uses the same envelope. On success:
Code
On failure:
Code
Branch on error.code (stable), not the message text (which may change).
One exception, at the gateway: a request to api.optimiciti.com with a missing or invalid API key is rejected by the gateway before it reaches the API, so a 401 (or 403) there is an RFC 7807 application/problem+json object (type/title/status/detail), not this { success, error } envelope. Once a request reaches the API, every response uses the envelope above.
Plans and limits
Your plan sets how large a problem can be, how many runs you can have in flight at once, and which premium features (such as road-network travel costs) you can use. See API access plans. When a request exceeds your plan, you get a 4xx with one of the codes below — each tells you exactly what to change.
What counts as a task. API plans include a number of optimization tasks per UTC calendar month. A task is one stop (vehicle-routing) or one trip (pickup-delivery) served by a solve that reached succeeded. Stops and trips the solver could not serve are free, and so are failed and cancelled runs, polling, and POST /cost_matrix. Every 202 from POST /problem carries data.usage (tasksUsed, tasksIncluded, resetsAt), and GET /usage returns the same object. The check at submission uses tasks already counted, not a guess of what the new run will use, so you can exceed the allowance by at most one run before MONTHLY_TASKS_EXHAUSTED stops the next submission.
Common error codes
| Code | Meaning | What to do |
|---|---|---|
CONCURRENCY_LIMIT (429) | Too many runs in flight for your plan. | Wait for one to finish, or cancel it, then retry. |
PLAN_LIMIT_EXCEEDED | The request uses a feature or size your plan doesn't include (error.details.feature names it). | Reduce the problem. |
PROBLEM_TOO_LARGE | The problem exceeds your plan's size cap. | Use fewer locations or vehicles. |
PAYLOAD_TOO_LARGE (413) | The request body is too large to accept. | Send fewer locations, or a smaller travel-cost table. |
UNKNOWN_FIELD (400) | A POST /problem or POST /cost_matrix body contains a field the API does not recognise -- misplaced (for example timeSettings inside problemData), misspelt, or from the other endpoint. It is refused rather than silently ignored, because an ignored field is a request that does not mean what you think. | error.details.unknownFields lists every one with its path and what to do: where the field belongs, or what it probably should be. |
RUN_NOT_FOUND / RESULTS_NOT_FOUND | That run, or its results, doesn't exist yet. | Check the id; results appear once a run succeeds. |
MISSING_TRAVEL_COSTS (400) | POST /problem received no problemData.travelCosts; it does not compute them. | Build a matrix with POST /cost_matrix and pass its travelCostResponses, or supply your own, then resubmit. |
MONTHLY_TASKS_EXHAUSTED (429) | Your plan's optimization tasks for this UTC month are used up (error.details carries tasksUsed, tasksIncluded, resetsAt). | Wait for the reset, or email [email protected] to raise the allowance. |
ANONYMOUS_LANE_BUSY (429) | Only signed-out web sessions can receive this: the capacity reserved for signed-out use is busy. A request carrying an API key never gets it. | Try again in a moment, or sign in. |
NON_FINITE_NUMBER (400) | The JSON body contains NaN, Infinity or -Infinity, which JSON does not define (error.details.paths lists where). | Send a finite number, or null for an unknown value. |
Transient 429, 502, 503, and 504 responses are safe to retry with backoff.
Links
- Optimiciti homepage
- API access plans
- Questions: [email protected]
