{
  "openapi": "3.0.3",
  "info": {
    "title": "Optimiciti API Specification",
    "description": "# Optimiciti API\n\nOptimiciti 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.\n\n> **Beta.** Endpoints and fields may still change. Using the API requires accepting our [terms of service](https://optimiciti.com/terms-of-service).\n\n## How it works\n\nA real optimization can take longer than a single request should wait, so solving is **asynchronous** — you submit, then poll:\n\n1. **Get travel costs (choose one).** Every solve needs the cost of travelling between your locations, and `POST /problem` does **not** compute them for you. Either (a) call `POST /cost_matrix` and pass its `travelCostResponses` as `problemData.travelCosts` — it computes them from coordinates by default (`autoCalculate*` defaults to `true`, `geo` method, no road-network call) — or (b) supply your own `travelCosts`. (There is no `costSettings` on `/problem` itself; 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 with `400 UNCOSTED_TRAVEL_MATRIX`, and an absent or empty one with `MISSING_TRAVEL_COSTS`. A matrix that is only partly filled is accepted, and the run `succeeded` with the stops behind the empty pairs left unassigned and no error. So read how many stops were assigned, not only the status.\n2. **Submit the problem.** `POST /problem` with your locations, vehicles, and constraints. You get an id back right away (the response calls it `run_id`); the solver runs in the background.\n3. **Poll for status.** `GET /problem/{problemId}` — the same id — or `POST /run_status`, until the run reaches a terminal state: `succeeded`, `failed`, or `cancelled`. 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 carries `status` and any `error` — never the routes (a `progress` key is present but always `null`).\n4. **Fetch the results.** Once the run is `succeeded`, get the optimized routes with `POST /run_results_by_run_id`. Results are a separate call on purpose — route payloads are large, and polling is frequent.\n\n(`problemId` in the path and `run_id` in responses are the **same identifier** — the two names refer to one run.)\n\nYour 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.\n\n## Authentication\n\nThere 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.**\n\nSend the key as a bearer credential in the `Authorization` header on every request:\n\n```\nAuthorization: Bearer <your-api-key>\n```\n\nFor example:\n\n```\ncurl https://api.optimiciti.com/user_runs \\\n  -X POST -H \"Authorization: Bearer <your-api-key>\" \\\n  -H \"Content-Type: application/json\" -d '{\"user_id\": \"me\"}'\n```\n\nYour API key, not this field, decides whose runs are returned.\n\nAn 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 [admin@optimiciti.com](mailto:admin@optimiciti.com). Once you have a key, the header above is all you need: there is nothing to activate.\n\nThe 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.\n\nOne 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.\n\n## Requests and responses\n\nEvery response uses the same envelope. On success:\n\n```json\n{ \"success\": true, \"data\": { \"...\": \"...\" } }\n```\n\nOn failure:\n\n```json\n{ \"success\": false, \"error\": { \"code\": \"PLAN_LIMIT_EXCEEDED\", \"message\": \"A human-readable reason.\" } }\n```\n\nBranch on `error.code` (stable), not the message text (which may change).\n\n**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.\n\n## Plans and limits\n\nYour 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](https://optimiciti.com/pricing). When a request exceeds your plan, you get a `4xx` with one of the codes below — each tells you exactly what to change.\n\n**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.\n\n## Common error codes\n\n| Code | Meaning | What to do |\n|---|---|---|\n| `CONCURRENCY_LIMIT` (429) | Too many runs in flight for your plan. | Wait for one to finish, or cancel it, then retry. |\n| `PLAN_LIMIT_EXCEEDED` | The request uses a feature or size your plan doesn't include (`error.details.feature` names it). | Reduce the problem. |\n| `PROBLEM_TOO_LARGE` | The problem exceeds your plan's size cap. | Use fewer locations or vehicles. |\n| `PAYLOAD_TOO_LARGE` (413) | The request body is too large to accept. | Send fewer locations, or a smaller travel-cost table. |\n| `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. |\n| `RUN_NOT_FOUND` / `RESULTS_NOT_FOUND` | That run, or its results, doesn't exist yet. | Check the id; results appear once a run succeeds. |\n| `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. |\n| `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 admin@optimiciti.com to raise the allowance. |\n| `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. |\n| `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. |\n\nTransient `429`, `502`, `503`, and `504` responses are safe to retry with backoff.\n\n## Links\n\n- [Optimiciti homepage](https://optimiciti.com)\n- [API access plans](https://optimiciti.com/pricing)\n- Questions: admin@optimiciti.com",
    "termsOfService": "https://optimiciti.com/terms-of-service",
    "contact": {
      "email": "admin@optimiciti.com"
    },
    "license": {
      "name": "Apache 2.0",
      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
    },
    "version": "0.0.2"
  },
  "servers": [
    {
      "url": "https://api.optimiciti.com"
    }
  ],
  "tags": [
    {
      "name": "Problem",
      "description": "Submit an optimization problem and follow it to completion. This is the core of the API - build your problem, submit it, poll for status, then read the optimized routes.",
      "externalDocs": {
        "description": "Model information",
        "url": "https://optimiciti.com"
      }
    },
    {
      "name": "Utilities",
      "description": "Health check - confirm the API is up and answering."
    },
    {
      "name": "Billing",
      "description": "Plans and limits - the public plan tiers, each with its limits and feature flags."
    },
    {
      "name": "History",
      "description": "Past runs and published examples - list a user's previous optimization runs, re-open their results, rename or delete them, and browse the example scenarios."
    },
    {
      "name": "Data-processing",
      "description": "Prepare travel data - build the travel-cost matrix (durations and distances between your locations) that a problem needs."
    },
    {
      "name": "Optimization",
      "description": "Run control by run id - check a run's status, or cancel it."
    },
    {
      "name": "System",
      "description": "Service root - a one-line banner confirming the service is reachable."
    }
  ],
  "paths": {
    "/problem": {
      "post": {
        "tags": [
          "Problem"
        ],
        "summary": "Submit an optimization run",
        "description": "Queues a vehicle-routing or pickup-delivery problem for the solver and returns immediately with `202 Accepted` and a `run_id` — it does **not** wait for the solve, and it does **not** compute travel costs. `/problem` uses the `problemData.travelCosts` you give it, so build that matrix first: either call `POST /cost_matrix` and pass its `travelCostResponses` straight into `problemData.travelCosts` (that endpoint computes from coordinates by default), or supply your own `travelCosts`. There is no `costSettings` on `/problem`; auto-calculation happens on `POST /cost_matrix`. An absent or empty matrix is rejected with `MISSING_TRAVEL_COSTS`, and a matrix with no usable pair at all with `UNCOSTED_TRAVEL_MATRIX` (see the 400 response); an all-zero matrix solves fine (every stop assigned). The solver treats a **null cost or duration** as unreachable, so a matrix that is only partly filled is accepted and the run `succeeds` with the stops behind the empty pairs left unassigned. Passing these checks is a floor, not a promise that every stop is assigned: read the assigned count in the results. See the warning on `POST /cost_matrix`. Poll `GET /problem/{problemId}` (or `POST /run_status`) until the run is terminal, then fetch results with `POST /run_results_by_run_id`.",
        "operationId": "problem",
        "requestBody": {
          "description": "The problem to solve. The example below is complete and runnable: a depot and two stops, one van, and a full `travelCosts` matrix (including the zero self-pairs). It returns a plan that visits both stops.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SolveRequest"
              },
              "example": {
                "userId": "acme-co",
                "problem": {
                  "problemType": "vehicle-routing",
                  "problemData": {
                    "locations": [
                      {
                        "locationId": "depot",
                        "locationName": "Depot",
                        "latitude": 39.7684,
                        "longitude": -86.1581,
                        "isDepot": true
                      },
                      {
                        "locationId": "a",
                        "locationName": "Stop A",
                        "latitude": 39.791,
                        "longitude": -86.148,
                        "demand": 1
                      },
                      {
                        "locationId": "b",
                        "locationName": "Stop B",
                        "latitude": 39.755,
                        "longitude": -86.175,
                        "demand": 1
                      }
                    ],
                    "vehicles": [
                      {
                        "vehicleId": "van-1",
                        "vehicleTypeId": "van",
                        "startLocationId": "depot",
                        "endLocationId": "depot"
                      }
                    ],
                    "vehicleTypes": [
                      {
                        "vehicleTypeId": "van",
                        "capacity": 10
                      }
                    ],
                    "travelCosts": [
                      {
                        "fromLocationId": "depot",
                        "toLocationId": "a",
                        "distance": 1.9,
                        "duration": 3.8,
                        "cost": 3.8
                      },
                      {
                        "fromLocationId": "a",
                        "toLocationId": "depot",
                        "distance": 1.9,
                        "duration": 3.8,
                        "cost": 3.8
                      },
                      {
                        "fromLocationId": "depot",
                        "toLocationId": "b",
                        "distance": 1.6,
                        "duration": 3.2,
                        "cost": 3.2
                      },
                      {
                        "fromLocationId": "b",
                        "toLocationId": "depot",
                        "distance": 1.6,
                        "duration": 3.2,
                        "cost": 3.2
                      },
                      {
                        "fromLocationId": "a",
                        "toLocationId": "b",
                        "distance": 3,
                        "duration": 6,
                        "cost": 6
                      },
                      {
                        "fromLocationId": "b",
                        "toLocationId": "a",
                        "distance": 3,
                        "duration": 6,
                        "cost": 6
                      },
                      {
                        "fromLocationId": "depot",
                        "toLocationId": "depot",
                        "distance": 0,
                        "duration": 0,
                        "cost": 0
                      },
                      {
                        "fromLocationId": "a",
                        "toLocationId": "a",
                        "distance": 0,
                        "duration": 0,
                        "cost": 0
                      },
                      {
                        "fromLocationId": "b",
                        "toLocationId": "b",
                        "distance": 0,
                        "duration": 0,
                        "cost": 0
                      }
                    ]
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Accepted. The run is queued and the solver runs in the background. The body carries `data.run_id` — the identifier you poll and fetch results with.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestAccepted"
                }
              }
            }
          },
          "400": {
            "description": "The request was structurally valid JSON but rejected by a business rule before queueing. The envelope's `error.code` names the specific reason: `UNKNOWN_FIELD` (a field the API does not recognise -- misplaced, misspelt, or belonging to another endpoint; `error.details.unknownFields` says where each one belongs), `NO_STOPS_TO_VISIT` (nothing for a vehicle to serve: a vehicle-routing problem whose every location is a depot, or a pickup-delivery problem with no trips), `MISSING_VEHICLES` (no `problemData.vehicles`), `UNKNOWN_VEHICLE_TYPE` (no vehicle's `vehicleTypeId` is defined in `problemData.vehicleTypes`. A vehicle whose type is undefined is never used, so when only some are undefined the problem solves with the rest of the fleet), `IGNORED_TIME_WINDOW` (a time value the solve would not read -- for example a clock time such as `9:00 AM` while `timeSettings.timeUnit` is `integer`, which is the default when `timeSettings` is omitted; `error.details.ignoredFields` lists each one), `INVALID_FIXED_ASSIGNMENT` (an impossible vehicle pin), `COORDINATE_SYSTEM_MISMATCH` / `MIXED_COORDINATE_SYSTEMS` (locations that do not match the declared coordinate system), `MISSING_TRAVEL_COSTS` (no `problemData.travelCosts` — `/problem` does not compute them), `UNCOSTED_TRAVEL_MATRIX` (a matrix is present but no pair between two different locations is usable, so the run would assign 0 stops. A pair is usable when it carries both a cost and a duration; the solver treats a missing one as unreachable. A row that carries a `vehicleTypeId` is accepted with a cost alone, but its missing duration is then taken as 0 minutes, so send durations whenever you use time windows. Do not mix the two kinds of row: as soon as any row carries a `vehicleTypeId`, the solver reads only the rows that have one, so rows without one are ignored and cannot make the matrix usable. Build the matrix with `POST /cost_matrix` or supply pairs with both values. An all-zero matrix is fine and is not rejected. One usable pair is enough to pass, so passing does not mean every stop will be assigned), or `MISSING_USER_ID` (a request with no API key and no token, and no `userId` in the body to fall back to — send a `userId` or call with a credential).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials. On `api.optimiciti.com` this is returned by the **gateway**, so its body is an RFC 7807 problem-detail object (`application/problem+json` with `type`/`title`/`status`/`detail`), **not** the standard `{ success, error }` envelope. Requests with no `Origin` header (curl, scripts, server-to-server) must carry a gateway API key; without one, authenticated tiers return this 401."
          },
          "403": {
            "description": "The request exceeds your plan. `error.code` is `PLAN_LIMIT_EXCEEDED` when it uses a feature or size your plan does not include (`error.details.feature` names it) or `PROBLEM_TOO_LARGE` when it exceeds your plan's size cap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "413": {
            "description": "The request body is too large to accept (`error.code = \"PAYLOAD_TOO_LARGE\"`). Send fewer locations, or a smaller travel-cost table.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "422": {
            "description": "The request body failed schema validation (a missing required field, a wrong type, or an out-of-range value). `error.code` is `VALIDATION_ERROR` and `error.details` lists the offending fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "429": {
            "description": "Refused by a limit, with `error.code` saying which. `CONCURRENCY_LIMIT`: the caller already has their plan's maximum number of solves in flight (queued + running); keep the run queued on your side and retry shortly. `MONTHLY_TASKS_EXHAUSTED`: the plan's optimization tasks for this UTC month are used up; `error.details` carries `tasksUsed`, `tasksIncluded` and `resetsAt`, and the message says how to continue. `ANONYMOUS_LANE_BUSY`: signed-out web sessions only; a request carrying an API key never receives it. None of these is a solve failure (a real failure surfaces later, asynchronously, as the run's status becoming `failed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/problem/{problemId}": {
      "get": {
        "tags": [
          "Problem"
        ],
        "summary": "Poll a run's status (never its routes)",
        "description": "Returns the lifecycle status of the run identified by `problemId` (`queued`, `running`, `succeeded`, `failed` or `cancelled`) and nothing else: **the routes are never in this response.** Once `data.status` is `succeeded`, fetch the plan with `POST /run_results_by_run_id`. Poll every 2 to 5 seconds: a tight loop gains nothing and will run into the gateway's rate limit (`429`).",
        "operationId": "problemStatus",
        "parameters": [
          {
            "name": "problemId",
            "in": "path",
            "description": "The problemId of a previously submitted problem to check the status of",
            "required": true,
            "explode": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The run's current lifecycle status. `data.status` is `queued`, `running`, or a terminal `succeeded` / `failed` / `cancelled`. This response never contains the routes — once `data.status` is `succeeded`, fetch the plan with `POST /run_results_by_run_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/RunStatus"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid problem ID specified."
          },
          "401": {
            "description": "Missing or invalid credentials (gateway RFC 7807 problem+json on `api.optimiciti.com`; see `POST /problem`)."
          },
          "404": {
            "description": "No run exists for that id (`error.code = \"RUN_NOT_FOUND\"`)."
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/problem/cancel/{problemId}": {
      "post": {
        "tags": [
          "Problem"
        ],
        "summary": "Cancel the optimization run identified by the problemId parameter",
        "description": "Cancel the run identified by the `problemId` path parameter. A queued run is skipped by the worker; a running run is cancelled best-effort. If the run has already reached a terminal state (`succeeded`, `failed`, or `cancelled`) this has no effect. `POST /cancel_run` (which takes the id in the body) does the same thing; use whichever fits your client.",
        "operationId": "cancelProblem",
        "parameters": [
          {
            "name": "problemId",
            "in": "path",
            "description": "The problemId of a previously submitted problem to cancel",
            "required": true,
            "explode": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The cancellation was accepted. The body is the standard envelope; `data` carries the run's current record, including its `status` after the cancel was applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid problem ID specified."
          },
          "401": {
            "description": "Missing or invalid credentials (gateway RFC 7807 problem+json on `api.optimiciti.com`; see `POST /problem`)."
          },
          "404": {
            "description": "No run exists for that id (`error.code = \"RUN_NOT_FOUND\"`)."
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/cost_matrix": {
      "post": {
        "tags": [
          "Data-processing"
        ],
        "summary": "Calculate a cost matrix for the given locations",
        "description": "Calculate a cost matrix for the given locations and travel costs. This is useful for calculating travel durations and distances between locations.",
        "operationId": "calculateCostMatrix",
        "requestBody": {
          "description": "The locations and cost settings to use for calculating the cost matrix. `costSettings` is required, and `costSettings.costMetric` is required within it — an empty `costSettings: {}` is rejected with 422. Optionally supply a partial `travelCosts` array to avoid recalculating pairs you already have.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CostMatrixRequest"
              },
              "example": {
                "locations": [
                  {
                    "locationId": "depot",
                    "locationName": "Depot",
                    "latitude": 39.7684,
                    "longitude": -86.1581,
                    "isDepot": true
                  },
                  {
                    "locationId": "a",
                    "locationName": "Stop A",
                    "latitude": 39.791,
                    "longitude": -86.148
                  },
                  {
                    "locationId": "b",
                    "locationName": "Stop B",
                    "latitude": 39.755,
                    "longitude": -86.175
                  }
                ],
                "costSettings": {
                  "costMetric": "duration",
                  "autoCalculateDistance": true,
                  "autoCalculateDuration": true,
                  "distanceMethod": "geo",
                  "durationMethod": "geo"
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Cost matrix calculated. `data.travelCostResponses` holds one entry per location pair (duration, distance, and cost); `data.travelCostErrors` lists any pairs that could not be computed. **This array drops straight into a solve: set `problemData.travelCosts` to `data.travelCostResponses` on your `POST /problem` request.**\n\n**Make sure durations come back populated.** With the defaults (no `travelCosts` supplied → `autoCalculate*` true, `geo`) every pair is computed. The trap is a matrix whose **durations are null**: the solver reads a null duration as an unreachable arc. You can produce that by sending `autoCalculateDuration: false` (or otherwise leaving durations empty). If that leaves no usable pair in the whole matrix, `POST /problem` rejects it with `400 UNCOSTED_TRAVEL_MATRIX` (its 400 response says what counts as usable). If only some pairs are empty, `/problem` accepts the matrix and returns a `succeeded` run with the stops behind those pairs **left unassigned** and no error. An all-*zero* matrix is not the same thing — it solves fine, with every stop assigned and 0 total distance. (An absent/empty matrix at `/problem` is rejected outright with `MISSING_TRAVEL_COSTS`.) So confirm the returned `duration` values are populated before solving, and read the assigned count in the results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/CostMatrixResponse"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected by a business rule (for example a coordinate system the locations do not match, or a `driving` method requested for coordinates ORS cannot route). `error.code` names the reason.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credentials. On `api.optimiciti.com` this is returned by the gateway as an RFC 7807 `application/problem+json` object, not the standard `{ success, error }` envelope (see `POST /problem`)."
          },
          "422": {
            "description": "The request body failed schema validation — most commonly a missing `costSettings` or a `costSettings` with no `costMetric`. `error.code` is `VALIDATION_ERROR` and `error.details` lists the offending fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "Read Root",
        "operationId": "read_root__get",
        "responses": {
          "200": {
            "description": "Successful Response"
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": []
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Utilities"
        ],
        "summary": "Health Check",
        "description": "Returns the operational status of the API.",
        "operationId": "health_check",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": []
      }
    },
    "/usage": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "This month's task usage",
        "description": "How many optimization tasks the calling account has used this UTC month, and how many its plan includes. The same object every `202` from `POST /problem` carries, on its own, for dashboards. Requires a verified identity: on `api.optimiciti.com` send your API key.",
        "operationId": "get_usage_usage_get",
        "responses": {
          "200": {
            "description": "This month's usage.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/UsageSummary"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No verified identity. On `api.optimiciti.com` this is returned by the gateway when the API key is missing or invalid (an RFC 7807 body), or by the API as `error.code = \"UNAUTHENTICATED\"`."
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          },
          "502": {
            "description": "Usage could not be read (`error.code = \"USAGE_UNAVAILABLE\"`). Not a zero: retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/plans": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "List public plans",
        "description": "Public source of truth for pricing and entitlements: the subscribable tiers, each with its display name, resolved limits, and feature flags. No auth and no request body. Front-end pricing tables and marketing JSON-LD should read limits from here instead of restating them. `data` is an array of `PublicPlan` objects.\n\nOptional display copy — `description` (string) and `highlights` (string[]) — is included only for tiers that declare it, so the pricing page can source prose here too rather than keeping a second copy. Absent rather than null when unwritten. This copy is configuration, not a live mirror of the billing provider: this endpoint deliberately makes no third-party call, so pricing stays up when billing does not.",
        "operationId": "list_plans",
        "responses": {
          "200": {
            "description": "The subscribable plan tiers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": []
      }
    },
    "/user_runs": {
      "post": {
        "tags": [
          "History"
        ],
        "summary": "Get User Runs",
        "description": "Get a list of all run IDs and timestamps for a user.",
        "operationId": "get_user_runs",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserHistoryRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/GatewayUnauthorized"
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/run_ids_by_user": {
      "post": {
        "tags": [
          "History"
        ],
        "summary": "Get Run Ids",
        "description": "Get the list of run IDs for a user.",
        "operationId": "get_run_ids",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserHistoryRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized request. The user does not have permission to access this resource."
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/run_summaries_by_user": {
      "post": {
        "tags": [
          "History"
        ],
        "summary": "Get Run Summaries",
        "description": "The caller's runs, newest first, one entry per run keyed by its `run_id`, with the run's lifecycle `status`, its submission time, its `summary`, and its `run_name` if it was renamed with `POST /update_run_name`. Since 2026-09-18 `run_datetime_unix` is an integer here, the same type `POST /user_runs` returns for the same row (it was a string). Runs finished before 2026-09-18 carry an empty `summary` (`{}`): the summary row was never written before then, and there is no backfill.",
        "operationId": "get_run_summaries",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserHistoryRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The caller's runs, newest first. `data` is an array; each element is an object with a single key, the `run_id`, whose value is a `RunSummaryEntry`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": {
                          "$ref": "#/components/schemas/RunSummaryEntry"
                        }
                      },
                      "example": [
                        {
                          "64d44bdd-e350-4c29-ad8a-a8af35c47b50": {
                            "run_datetime_unix": 1789703114,
                            "status": "succeeded",
                            "summary": {},
                            "run_name": "dod3-run-2"
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/GatewayUnauthorized"
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/list_examples": {
      "post": {
        "tags": [
          "History"
        ],
        "summary": "List Example Scenarios",
        "description": "List the published example scenarios available to all users. Load one in full with `GET /example_scenario/{slug}`, using its `slug`.",
        "operationId": "list_examples",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": []
      }
    },
    "/run_results_by_run_id": {
      "post": {
        "tags": [
          "History"
        ],
        "summary": "Get Run Results",
        "description": "Get the full results of a specific optimization run.",
        "operationId": "get_run_results",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunIdRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The optimized plan. `data` is a `ProblemResponse` with two parts: `data.problemResult` (the routes — read them from `data.problemResult.routeStopsFull`) and `data.resultSummary` (assignment/route/cost/timing totals). Before trusting the result, check that nothing was left unassigned: in `data.resultSummary.assignmentSummary.locationsAssigned`, `assigned` must equal `total`. (`total` does not count depots, so it is not the number of locations you sent.) To check independently, every non-depot location you submitted should appear as a stop in `data.problemResult.routeStopsFull`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/ProblemResponse"
                    }
                  }
                },
                "example": {
                  "success": true,
                  "data": {
                    "problemResult": {
                      "problemId": "550e8400-e29b-41d4-a716-446655440000",
                      "problemStatus": {
                        "status": "completed"
                      },
                      "routeStopsFull": [
                        {
                          "stopId": "7ee56062-799f-4041-a7f9-b1b70daff695",
                          "locationId": "depot",
                          "locationName": "Depot",
                          "color": "#FFFFFF",
                          "vehicleId": "van-1",
                          "stopType": "route_start",
                          "stopNumber": 0,
                          "arrivalTimeInt": null,
                          "arrivalTimeStr": null,
                          "travelCostTo": 3.2,
                          "travelCostFromPreviousStop": 0,
                          "serviceDuration": 0,
                          "capacityServed": 0
                        },
                        {
                          "stopId": "7568253d-5140-44ae-9bf0-d3661bdf6494",
                          "locationId": "b",
                          "locationName": "Stop B",
                          "color": "#FFFFFF",
                          "vehicleId": "van-1",
                          "stopType": "location_visit",
                          "stopNumber": 1,
                          "arrivalTimeInt": 3,
                          "arrivalTimeStr": null,
                          "travelCostTo": 6,
                          "travelCostFromPreviousStop": 3.2,
                          "serviceDuration": 0,
                          "capacityServed": 1
                        },
                        {
                          "stopId": "84a10653-c521-4859-a724-7f00191d2333",
                          "locationId": "a",
                          "locationName": "Stop A",
                          "color": "#FFFFFF",
                          "vehicleId": "van-1",
                          "stopType": "location_visit",
                          "stopNumber": 2,
                          "arrivalTimeInt": 9,
                          "arrivalTimeStr": null,
                          "travelCostTo": 3.8,
                          "travelCostFromPreviousStop": 6,
                          "serviceDuration": 0,
                          "capacityServed": 1
                        },
                        {
                          "stopId": "d5bb9496-e72a-45be-b687-cf8619a7fd0b",
                          "locationId": "depot",
                          "locationName": "Depot",
                          "color": "#FFFFFF",
                          "vehicleId": "van-1",
                          "stopType": "route_end",
                          "stopNumber": 3,
                          "arrivalTimeInt": 13,
                          "arrivalTimeStr": null,
                          "travelCostTo": 0,
                          "travelCostFromPreviousStop": 3.8,
                          "serviceDuration": 0,
                          "capacityServed": 0
                        }
                      ],
                      "routeStops": null,
                      "unassignedLocations": null,
                      "paths": [
                        {
                          "vehicle_id": "van-1",
                          "path": "[{\"Leg\": 0, \"fromLocationId\": \"depot\", \"fromLatitude\": 39.7684, \"fromLongitude\": -86.1581, \"toLocationId\": \"b\", \"toLatitude\": 39.755, \"toLongitude\": -86.175}]"
                        }
                      ]
                    },
                    "resultSummary": {
                      "assignmentSummary": {
                        "vehiclesAssigned": {
                          "assigned": 1,
                          "available": 1
                        },
                        "locationsAssigned": {
                          "assigned": 2,
                          "total": 2
                        }
                      },
                      "routeSummary": {
                        "totalDistanceTraveled": 6.5,
                        "totalMinutesTraveled": 13,
                        "totalTravelCost": 13
                      },
                      "costSummary": {
                        "fixedCost": 0,
                        "travelCost": 13
                      },
                      "timingSummary": {
                        "solveTime": 0.07
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/GatewayUnauthorized"
          },
          "404": {
            "description": "No results for that id yet (`error.code` is `RESULTS_NOT_FOUND` when the run has not produced results, `RUN_NOT_FOUND` when the id is unknown). `error.details.status` says why there are none -- the run is still `queued` or `running`, was `cancelled`, or `failed` -- and for a failed run `error.details.error_code` carries the same code as the run's status (see `RunStatus.error_code`), while `error.message` includes its reason."
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/run_status": {
      "post": {
        "tags": [
          "Optimization"
        ],
        "summary": "Get Run Status",
        "description": "Returns the lifecycle status of a run (queued | running | succeeded | failed | cancelled) for polling. Poll every 2 to 5 seconds: a tight loop gains nothing and will run into the gateway's rate limit (`429`).",
        "operationId": "run_status",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunIdRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The run's current lifecycle status. `data.status` is `queued`, `running`, or a terminal `succeeded` / `failed` / `cancelled`. The response never carries the routes — fetch those from `POST /run_results_by_run_id` once the run has succeeded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/RunStatus"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/GatewayUnauthorized"
          },
          "404": {
            "description": "No run exists for that id (`error.code = \"RUN_NOT_FOUND\"`)."
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/cancel_run": {
      "post": {
        "tags": [
          "Optimization"
        ],
        "summary": "Cancel Run",
        "description": "Cancels a queued or running solve. Queued runs are skipped by the worker; running runs are cancelled best-effort.",
        "operationId": "cancel_run",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunIdRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/GatewayUnauthorized"
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/example_scenario/{slug}": {
      "get": {
        "tags": [
          "History"
        ],
        "summary": "Get a published example scenario",
        "description": "Returns the full input state of a **published example**, addressed by its slug (as returned by `POST /list_examples`).\n\nExamples are public and immutable between seeds, so this route is a `GET` keyed entirely by its URL and carries a `Cache-Control` header — a gateway or CDN can serve it without inspecting a request body or knowing who is asking.\n\nIt can only ever return an example: the slug is resolved through the example index and the resolved record's owner is re-checked against the examples account, so a private scenario cannot be served from this path even if the index were wrong.",
        "operationId": "get_example_scenario",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The example's slug, as published on /list_examples (e.g. `tokyo-home-health-occupational-therapy`).",
            "required": true,
            "explode": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "404": {
            "description": "No published example exists with that slug."
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": []
      }
    },
    "/update_run_name": {
      "post": {
        "tags": [
          "History"
        ],
        "summary": "Update Run Name",
        "description": "Update the name of a specific run.",
        "operationId": "update_run_name",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateRunRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/GatewayUnauthorized"
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    },
    "/delete_run": {
      "post": {
        "tags": [
          "History"
        ],
        "summary": "Delete Run",
        "description": "Delete a specific optimization run.",
        "operationId": "delete_run",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunIdRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StandardResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/GatewayUnauthorized"
          },
          "429": {
            "$ref": "#/components/responses/GatewayRateLimited"
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "SolveRequest": {
        "additionalProperties": false,
        "description": "The complete request payload for the /problem endpoint.",
        "type": "object",
        "required": [
          "problem"
        ],
        "properties": {
          "userId": {
            "type": "string",
            "nullable": true,
            "description": "Your identity for ownership comes from your API key (or Clerk token), never from this field, so a run is owned by the credential that submitted it — `POST /user_runs` returns the runs your key made regardless of what `userId` you send. **Required only when you call without a credential:** an API-key or token request may omit it (the value is then ignored), but a request with neither a key nor a token must supply a non-empty `userId` as a self-assigned id, or it is rejected with `400 MISSING_USER_ID`. The browser app's legacy soft-auth path uses it this way."
          },
          "problem": {
            "$ref": "#/components/schemas/ProblemRequest"
          },
          "exampleSlug": {
            "type": "string",
            "description": "Optional. The slug of the published example scenario this solve was loaded from (as returned by /list_examples). Published examples legitimately exceed the free tier's size limits, so sending this grants a bounded allowance sized from that example's own row counts plus a small edit margin - enough to run and tweak the demo, and never more than the named example's real size. Omit it for a user's own scenarios; an unrecognised slug is ignored."
          }
        }
      },
      "ProblemRequest": {
        "additionalProperties": false,
        "description": "The Problem object embedded within the main solve request.",
        "required": [
          "problemType",
          "problemData"
        ],
        "type": "object",
        "properties": {
          "problemType": {
            "type": "string",
            "enum": [
              "pickup-delivery",
              "vehicle-routing"
            ],
            "example": "vehicle-routing"
          },
          "problemData": {
            "$ref": "#/components/schemas/ProblemData"
          },
          "timeSettings": {
            "$ref": "#/components/schemas/TimeSettings"
          },
          "coordinateSystem": {
            "$ref": "#/components/schemas/CoordinateSystem"
          }
        }
      },
      "StandardResponse": {
        "type": "object",
        "description": "A standardized response envelope used across the API.",
        "properties": {
          "success": {
            "type": "boolean",
            "title": "Success",
            "description": "Indicates if the request was successful",
            "default": true
          },
          "data": {
            "title": "Data",
            "description": "The main response payload. Can be an object, array, boolean, or string.",
            "nullable": true
          },
          "error": {
            "$ref": "#/components/schemas/ResponseError"
          }
        }
      },
      "ResponseError": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "title": "Code",
            "description": "Internal error code"
          },
          "message": {
            "type": "string",
            "title": "Message",
            "description": "Human readable error message"
          },
          "details": {
            "type": "object",
            "title": "Details",
            "description": "Additional error context",
            "nullable": true
          }
        }
      },
      "RunIdRequest": {
        "type": "object",
        "required": [
          "run_id"
        ],
        "properties": {
          "run_id": {
            "type": "string",
            "description": "The unique Run ID"
          }
        }
      },
      "RunItem": {
        "type": "object",
        "required": [
          "run_id",
          "run_datetime_unix"
        ],
        "properties": {
          "run_id": {
            "type": "string"
          },
          "run_datetime_unix": {
            "type": "integer"
          }
        }
      },
      "RunSummaryEntry": {
        "description": "One run in `POST /run_summaries_by_user`, keyed by its `run_id` in the enclosing object.",
        "type": "object",
        "required": [
          "run_datetime_unix",
          "status",
          "summary"
        ],
        "properties": {
          "run_datetime_unix": {
            "type": "integer",
            "nullable": true,
            "description": "When the run was submitted, as a Unix timestamp in seconds. An integer, the same type `POST /user_runs` returns for this run.",
            "example": 1789703114
          },
          "status": {
            "type": "string",
            "description": "The run's lifecycle status, the same value `POST /run_status` reports: `queued`, `running`, `succeeded`, `failed` or `cancelled`. Runs from before the lifecycle field existed are all finished and read `succeeded`.",
            "example": "succeeded",
            "enum": [
              "queued",
              "running",
              "succeeded",
              "failed",
              "cancelled"
            ]
          },
          "summary": {
            "type": "object",
            "description": "A compact summary of the finished run: the `assignmentSummary`, `routeSummary` and `costSummary` parts of the result's `resultSummary`, key for key (the full result, including `timingSummary`, is in `POST /run_results_by_run_id`). Empty (`{}`) for runs finished before 2026-09-18, which predate the summary being written; there is no backfill.",
            "additionalProperties": true
          },
          "run_name": {
            "type": "string",
            "description": "The name given with `POST /update_run_name`. Absent until the run has been renamed.",
            "example": "Tuesday routes"
          }
        }
      },
      "UpdateRunRequest": {
        "type": "object",
        "required": [
          "run_id",
          "run_name"
        ],
        "properties": {
          "run_id": {
            "type": "string"
          },
          "run_name": {
            "type": "string"
          }
        }
      },
      "UserHistoryRequest": {
        "type": "object",
        "required": [
          "user_id"
        ],
        "properties": {
          "user_id": {
            "type": "string",
            "description": "The unique User ID"
          }
        }
      },
      "UserRunsResponse": {
        "type": "object",
        "required": [
          "runs"
        ],
        "properties": {
          "runs": {
            "items": {
              "$ref": "#/components/schemas/RunItem"
            },
            "type": "array"
          }
        }
      },
      "ProblemData": {
        "additionalProperties": false,
        "description": "This schema defines the problem data for an optimization Problem. The particular data objects used will depend on the problemType.",
        "type": "object",
        "properties": {
          "locations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Location"
            }
          },
          "trips": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Trip"
            }
          },
          "vehicles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Vehicle"
            }
          },
          "vehicleTypes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VehicleType"
            }
          },
          "travelCosts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TravelCost"
            }
          }
        }
      },
      "TimeSettings": {
        "additionalProperties": false,
        "description": "This schema defines how to interpet the input time field settings. Optimiciti allows for input times to be specified as absolute numeric values (i.e 0, 12, 60, etc.) or as date/time values (i.e. 1/1/2024, 2:24pm).",
        "type": "object",
        "properties": {
          "timeUnit": {
            "description": "The time unit to use for optimization. This time unit will apply, as applicable, to location open and close windows, trip open and close windows, vehicle operating times, and travel durations. Integer (default) indicates that all times are in absolute time units from an arbitrarily defined time 0. Clock indicates that times will be interpreted as literal clock times and calendar dates. Which time fields are READ depends on this: `integer` reads only the `*TimeInt` fields; `clock` reads only the `*TimeStr` fields, plus `*DayInt` as a day offset to them. **`integer` is also what you get when `timeSettings` is omitted**, so clock times such as `9:00 AM` need `timeSettings` with `timeUnit: clock` at the top level of the problem (not inside `problemData`). A request whose time values would be ignored under the unit it declares is refused with 400 `IGNORED_TIME_WINDOW`.",
            "type": "string",
            "enum": [
              "integer",
              "clock"
            ],
            "default": "integer"
          },
          "timeFormat": {
            "type": "string",
            "default": null,
            "example": "I:%M%p",
            "description": "The format to expect all time fields in. This field is only relevant when timeUnit = \"clock\". Formattings should match the formats described by the python [pandas.Period.strftime class](https://pandas.pydata.org/docs/reference/api/pandas.Period.strftime.html). For example, if the format used matches 09:32pm, then '%I:%M%p'. If null is passed, then the [pandas.to_datetime function](https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html) will be used to detect the format."
          },
          "multipleDays": {
            "default": false,
            "description": "A boolean field indicating whether to allow the optimization time period to extend multiple days. This field is relevant only when timeUnit = \"datetime\"."
          },
          "dateUnit": {
            "type": "string",
            "enum": [
              "integer",
              "calendar"
            ],
            "default": "integer",
            "description": "The date unit to use for optimization. This field is only relevant when timeUnit = \"clock\" and multipleDays = true. If this field is integer, then days will be measured as absolute days from an arbitrarily defined day 0. Calendar indidates that dates will be interpreted as literal calendar dates."
          },
          "dateFormat": {
            "type": "string",
            "default": null,
            "example": "%Y-%m-%d",
            "description": "The format to expect all dates fields in. This field is only relevant when dateUnit = \"calendar\", multipleDays = true, and timeUnit = \"clock\". Formattings should match the formats described by the python [pandas.Period.strftime class](https://pandas.pydata.org/docs/reference/api/pandas.Period.strftime.html). For example, if the format used matches 2024-12-31, then '%Y-%m-%d' should be passed for this parameter. If null is passed, then the [pandas.to_datetime function](https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html) will be used to detect the format."
          }
        }
      },
      "Location": {
        "additionalProperties": false,
        "required": [
          "locationId",
          "locationName"
        ],
        "properties": {
          "locationId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "A string that uniquely identifies this location."
          },
          "locationName": {
            "type": "string",
            "example": "Chicago",
            "description": "A name for this location. Required: a request whose location has no `locationName` is refused with `422`. It is echoed on every route stop that visits the location (`RouteStop.locationName`) and on `unassignedLocationsFull`, so a route sheet is readable without re-joining the input."
          },
          "demand": {
            "description": "The amount of vehicle capacity this location consumes",
            "type": "integer",
            "example": 10,
            "minimum": 0,
            "default": 1
          },
          "latitude": {
            "description": "The latitude for this location. Required only if autoCalculateDuration or autoCalculateDistance is true.",
            "type": "number",
            "format": "float",
            "minimum": -90,
            "maximum": 90,
            "example": 39.791
          },
          "longitude": {
            "description": "The longitude for this location. Required only if autoCalculateDuration or autoCalculateDistance is true.",
            "type": "number",
            "format": "float",
            "minimum": -180,
            "maximum": 180,
            "example": -86.148003
          },
          "x": {
            "type": "number",
            "nullable": true,
            "example": 1250.5,
            "description": "Cartesian x position on an arbitrary plane (warehouse floor, factory layout, PCB). Unbounded - deliberately NOT range-validated, unlike longitude. Use with coordinateSystem=cartesian; x is the exact counterpart of longitude (x = longitude, y = latitude), so a scenario can migrate between the two families without reordering. Supply either latitude/longitude or x/y, never both."
          },
          "y": {
            "type": "number",
            "nullable": true,
            "example": 880.25,
            "description": "Cartesian y position on an arbitrary plane. Unbounded - deliberately NOT range-validated, unlike latitude. Use with coordinateSystem=cartesian; y is the exact counterpart of latitude. Supply either latitude/longitude or x/y, never both."
          },
          "isDepot": {
            "description": "Whether this location serves only as a depot for vehicles (true) or if this location should be serviced.",
            "type": "boolean",
            "default": false
          },
          "serviceDuration": {
            "description": "How long service at this location takes, in the problem's time unit (minutes under `timeSettings.timeUnit: clock`; your integer unit otherwise). Charged at the stop; defaults to 0.",
            "type": "number",
            "format": "float",
            "minimum": 0,
            "example": 15.5,
            "default": 0
          },
          "minArrivalTimeInt": {
            "description": "An integer value representing the earliest time this location can be serviced (time t=0 is the earliest start time). Leave empty if there are no time restrictions on arrival. **Read only when `timeSettings.timeUnit` is `integer`** (the default, including when `timeSettings` is omitted). Under `clock` it is ignored and `minArrivalTimeStr` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "integer",
            "minimum": 0,
            "example": 10,
            "nullable": true,
            "default": null
          },
          "minArrivalTimeStr": {
            "description": "A string value representing the earliest time this location can be serviced (time t=0 is the earliest start time). Leave empty if there are no time restrictions on arrival. The time format should correspond to the timeFormat field in timeSettings (at the top level of the problem, not inside problemData). **Read only when `timeSettings.timeUnit` is `clock`.** The default is `integer`, including when `timeSettings` is omitted, and then this field is ignored and `minArrivalTimeInt` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "string",
            "example": "2024-01-01 12:00:00am",
            "nullable": true,
            "default": null
          },
          "minArrivalDayInt": {
            "description": "An integer value representing the earliest day this location can be serviced. Read only when `timeSettings.timeUnit` is `clock`, as a day offset added to the matching `minArrivalTimeStr` time; ignored under `integer` or without that time, and a request that sets it where it would be ignored is refused with 400 `IGNORED_TIME_WINDOW`. Leave empty if there are no restrictions on arrival.",
            "type": "integer",
            "minimum": 0,
            "example": 1,
            "nullable": true,
            "default": null
          },
          "maxArrivalTimeInt": {
            "description": "An integer value representing the latest time this location can be serviced (time t=0 is the earliest start time). Leave empty if there are no time restrictions on arrival. **Read only when `timeSettings.timeUnit` is `integer`** (the default, including when `timeSettings` is omitted). Under `clock` it is ignored and `maxArrivalTimeStr` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "integer",
            "minimum": 0,
            "example": 100,
            "nullable": true,
            "default": null
          },
          "maxArrivalTimeStr": {
            "description": "A string value representing the latest time this location can be serviced (time t=0 is the earliest start time). Leave empty if there are no time restrictions on arrival. The time format should correspond to the timeFormat field in timeSettings (at the top level of the problem, not inside problemData). **Read only when `timeSettings.timeUnit` is `clock`.** The default is `integer`, including when `timeSettings` is omitted, and then this field is ignored and `maxArrivalTimeInt` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "string",
            "example": "2024-01-01 11:59:59pm",
            "nullable": true,
            "default": null
          },
          "maxArrivalDayInt": {
            "description": "An integer value representing the latest day this location can be serviced. Read only when `timeSettings.timeUnit` is `clock`, as a day offset added to the matching `maxArrivalTimeStr` time; ignored under `integer` or without that time, and a request that sets it where it would be ignored is refused with 400 `IGNORED_TIME_WINDOW`. Leave empty if there are no restrictions on arrival.",
            "type": "integer",
            "minimum": 0,
            "example": 1,
            "nullable": true,
            "default": null
          },
          "exactArrivalTimeInt": {
            "description": "An integer value representing the exact time this location must be serviced (time t=0 is the earliest start time). Leave empty if there are no time restrictions on arrival. This is a HARD PIN: the solver must arrive at exactly this time, applied as an equal min/max window. Set EITHER the exact* fields OR the min*/max* window for this location, never both. These are checked when the run starts, not when it is submitted: `POST /problem` accepts the request (`202`), and the run then ends with `status: failed`, an `error` saying what to fix and an `error_code` (`EXACT_ARRIVAL_CONFLICT`, `EXACT_ARRIVAL_INFEASIBLE` or `EXACT_ARRIVAL_UNSERVABLE`; see `RunStatus.error_code`), never by silently dropping the stop -- if the exact time and a window are both set; if no vehicle can reach it in time (the error names the earliest feasible time); or if it is reachable on its own but unsatisfiable with the rest of the problem (fleet size, capacity, competing pins). **Read only when `timeSettings.timeUnit` is `integer`** (the default, including when `timeSettings` is omitted). Under `clock` it is ignored and `exactArrivalTimeStr` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "integer",
            "minimum": 0,
            "example": 50,
            "nullable": true,
            "default": null
          },
          "exactArrivalTimeStr": {
            "description": "A string value representing the exact time this location must be serviced (time t=0 is the earliest start time). Leave empty if there are no time restrictions on arrival. The time format should correspond to the timeFormat field in timeSettings (at the top level of the problem, not inside problemData). This is a HARD PIN: the solver must arrive at exactly this time, applied as an equal min/max window. Set EITHER the exact* fields OR the min*/max* window for this location, never both. These are checked when the run starts, not when it is submitted: `POST /problem` accepts the request (`202`), and the run then ends with `status: failed`, an `error` saying what to fix and an `error_code` (`EXACT_ARRIVAL_CONFLICT`, `EXACT_ARRIVAL_INFEASIBLE` or `EXACT_ARRIVAL_UNSERVABLE`; see `RunStatus.error_code`), never by silently dropping the stop -- if the exact time and a window are both set; if no vehicle can reach it in time (the error names the earliest feasible time); or if it is reachable on its own but unsatisfiable with the rest of the problem (fleet size, capacity, competing pins). **Read only when `timeSettings.timeUnit` is `clock`.** The default is `integer`, including when `timeSettings` is omitted, and then this field is ignored and `exactArrivalTimeInt` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "string",
            "example": "2024-01-01 01:00:00am",
            "nullable": true,
            "default": null
          },
          "exactArrivalDayInt": {
            "description": "An integer value representing the exact day this location must be serviced. Read only when `timeSettings.timeUnit` is `clock`, as a day offset added to the matching `exactArrivalTimeStr` time; ignored under `integer` or without that time, and a request that sets it where it would be ignored is refused with 400 `IGNORED_TIME_WINDOW`. Leave empty if there are no restrictions on arrival. This is a HARD PIN: the solver must arrive at exactly this time, applied as an equal min/max window. Set EITHER the exact* fields OR the min*/max* window for this location, never both. These are checked when the run starts, not when it is submitted: `POST /problem` accepts the request (`202`), and the run then ends with `status: failed`, an `error` saying what to fix and an `error_code` (`EXACT_ARRIVAL_CONFLICT`, `EXACT_ARRIVAL_INFEASIBLE` or `EXACT_ARRIVAL_UNSERVABLE`; see `RunStatus.error_code`), never by silently dropping the stop -- if the exact time and a window are both set; if no vehicle can reach it in time (the error names the earliest feasible time); or if it is reachable on its own but unsatisfiable with the rest of the problem (fleet size, capacity, competing pins).",
            "type": "integer",
            "minimum": 0,
            "example": 1,
            "nullable": true,
            "default": null
          },
          "color": {
            "description": "The color to use for this location in the output. Accepts a hex code (\"#FF0000\") or a CSS color name (\"crimson\").",
            "type": "string",
            "example": "#FF0000"
          },
          "vehicleTypeIds": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "550e8400-e29b-41d4-a716-446655440000"
            },
            "example": [
              "550e8400-e29b-41d4-a716-446655440000",
              "550e8400-e29b-41d4-a716-446655440001"
            ],
            "nullable": true,
            "description": "The vehicleTypeIds that can service this location. These vehicleTypeIds must exist in the VehicleTypes passed in ProblemData for this problem. Leave empty if there are no restrictions on which vehicle types can service this location."
          },
          "fixedVehicleId": {
            "type": "string",
            "nullable": true,
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "Optional. Pin this location to a specific vehicle: the solve must use this vehicle to serve this stop. The vehicleId must exist in the Vehicles passed in ProblemData. If the pin cannot be satisfied - the vehicle does not exist, or its type is excluded by this location's vehicleTypeIds - the location is returned unassigned rather than served by a different vehicle. Leave null for no pin."
          }
        }
      },
      "Trip": {
        "additionalProperties": false,
        "required": [
          "tripId",
          "fromLocationId",
          "toLocationId"
        ],
        "properties": {
          "tripId": {
            "type": "string",
            "example": "1",
            "description": "A unique identifier for this trip. **It must be a whole number written as a string, for example `\"1\"`, with no leading zeros.** Any other value (a UUID, `\"trip-1\"`, `\"1.5\"`) makes the run fail, and a padded one (`\"007\"`) comes back as `\"7\"`."
          },
          "tripName": {
            "type": "string",
            "example": "Trip 1",
            "description": "A name for this trip (optional)."
          },
          "fromLocationId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "The locationId for the origin location of this trip. This locationId must exist in the Locations passed in ProblemData for this problem."
          },
          "toLocationId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440001",
            "description": "The locationId for the destination location of this trip. This locationId must exist in the Locations passed in ProblemData for this problem."
          },
          "vehicleTypeIds": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "550e8400-e29b-41d4-a716-446655440000"
            },
            "example": [
              "550e8400-e29b-41d4-a716-446655440000",
              "550e8400-e29b-41d4-a716-446655440001"
            ],
            "description": "The vehicleTypeIds that can be used to service this trip. These vehicleTypeIds must exist in the VehicleTypes passed in ProblemData for this problem."
          },
          "fixedVehicleId": {
            "type": "string",
            "nullable": true,
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "Optional. Pin this trip to a specific vehicle: the solve must use this vehicle for both the pickup and the delivery. The vehicleId must exist in the Vehicles passed in ProblemData. If the pin cannot be satisfied - the vehicle does not exist, or its type is excluded by this trip's vehicleTypeIds - the trip is returned unassigned rather than served by a different vehicle. Leave null for no pin."
          },
          "minPickupTimeInt": {
            "description": "An integer value representing the earliest time this trip can be picked up (time t=0 is the earliest start time). Leave empty if there are no time restrictions on pickup. **Read only when `timeSettings.timeUnit` is `integer`** (the default, including when `timeSettings` is omitted). Under `clock` it is ignored and `minPickupTimeStr` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "integer",
            "minimum": 0,
            "example": 10,
            "nullable": true,
            "default": null
          },
          "minPickupTimeStr": {
            "description": "A string value representing the earliest time this trip can be picked up. Leave empty if there are no time restrictions on pickup. The time format should correspond to the timeFormat field in timeSettings (at the top level of the problem, not inside problemData). **Read only when `timeSettings.timeUnit` is `clock`.** The default is `integer`, including when `timeSettings` is omitted, and then this field is ignored and `minPickupTimeInt` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "string",
            "example": "2024-01-01 12:00:00am",
            "nullable": true,
            "default": null
          },
          "minPickupDayInt": {
            "description": "An integer value representing the earliest day this trip can be picked up. Read only when `timeSettings.timeUnit` is `clock`, as a day offset added to the matching `minPickupTimeStr` time; ignored under `integer` or without that time, and a request that sets it where it would be ignored is refused with 400 `IGNORED_TIME_WINDOW`. Leave empty if there are no restrictions on pickup.",
            "type": "integer",
            "minimum": 0,
            "example": 1,
            "nullable": true,
            "default": null
          },
          "maxPickupTimeInt": {
            "description": "An integer value representing the latest time this trip can be picked up (time t=0 is the earliest start time). Leave empty if there are no time restrictions on pickup. **Read only when `timeSettings.timeUnit` is `integer`** (the default, including when `timeSettings` is omitted). Under `clock` it is ignored and `maxPickupTimeStr` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "integer",
            "minimum": 0,
            "example": 100,
            "nullable": true,
            "default": null
          },
          "maxPickupTimeStr": {
            "description": "A string value representing the latest time this trip can be picked up. Leave empty if there are no time restrictions on pickup. The time format should correspond to the timeFormat field in timeSettings (at the top level of the problem, not inside problemData). **Read only when `timeSettings.timeUnit` is `clock`.** The default is `integer`, including when `timeSettings` is omitted, and then this field is ignored and `maxPickupTimeInt` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "string",
            "example": "2024-01-01 11:59:59pm",
            "nullable": true,
            "default": null
          },
          "maxPickupDayInt": {
            "description": "An integer value representing the latest day this trip can be picked up. Read only when `timeSettings.timeUnit` is `clock`, as a day offset added to the matching `maxPickupTimeStr` time; ignored under `integer` or without that time, and a request that sets it where it would be ignored is refused with 400 `IGNORED_TIME_WINDOW`. Leave empty if there are no restrictions on pickup.",
            "type": "integer",
            "minimum": 0,
            "example": 1,
            "nullable": true,
            "default": null
          },
          "exactPickupTimeInt": {
            "description": "An integer value representing the exact time this trip must be picked up (time t=0 is the earliest start time). Leave empty if there are no time restrictions on pickup. This is a HARD PIN: the solver must pick up at exactly this time, applied as an equal min/max window. Set EITHER the exact* fields OR the min*/max* window for the same end, never both. These are checked when the run starts, not when it is submitted: `POST /problem` accepts the request (`202`), and the run then ends with `status: failed`, an `error` saying what to fix and an `error_code` (`EXACT_ARRIVAL_CONFLICT`, `EXACT_ARRIVAL_INFEASIBLE` or `EXACT_ARRIVAL_UNSERVABLE`; see `RunStatus.error_code`), never by silently dropping the trip -- if the exact time and a window are both set; if no vehicle can reach it in time (the error names the earliest feasible time); or if it is reachable on its own but unsatisfiable with the rest of the problem (fleet size, capacity, competing pins). **Read only when `timeSettings.timeUnit` is `integer`** (the default, including when `timeSettings` is omitted). Under `clock` it is ignored and `exactPickupTimeStr` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "integer",
            "minimum": 0,
            "example": 15,
            "nullable": true,
            "default": null
          },
          "exactPickupTimeStr": {
            "description": "A string value representing the exact time this trip must be picked up. Leave empty if there are no time restrictions on pickup. The time format should correspond to the timeFormat field in timeSettings (at the top level of the problem, not inside problemData). This is a HARD PIN: the solver must pick up at exactly this time, applied as an equal min/max window. Set EITHER the exact* fields OR the min*/max* window for the same end, never both. These are checked when the run starts, not when it is submitted: `POST /problem` accepts the request (`202`), and the run then ends with `status: failed`, an `error` saying what to fix and an `error_code` (`EXACT_ARRIVAL_CONFLICT`, `EXACT_ARRIVAL_INFEASIBLE` or `EXACT_ARRIVAL_UNSERVABLE`; see `RunStatus.error_code`), never by silently dropping the trip -- if the exact time and a window are both set; if no vehicle can reach it in time (the error names the earliest feasible time); or if it is reachable on its own but unsatisfiable with the rest of the problem (fleet size, capacity, competing pins). **Read only when `timeSettings.timeUnit` is `clock`.** The default is `integer`, including when `timeSettings` is omitted, and then this field is ignored and `exactPickupTimeInt` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "string",
            "example": "2024-01-01 12:15:00am",
            "nullable": true,
            "default": null
          },
          "exactPickupDayInt": {
            "description": "An integer value representing the exact day this trip must be picked up. Leave empty if there are no restrictions on pickup. This is a HARD PIN: the solver must pick up at exactly this time, applied as an equal min/max window. Set EITHER the exact* fields OR the min*/max* window for the same end, never both. These are checked when the run starts, not when it is submitted: `POST /problem` accepts the request (`202`), and the run then ends with `status: failed`, an `error` saying what to fix and an `error_code` (`EXACT_ARRIVAL_CONFLICT`, `EXACT_ARRIVAL_INFEASIBLE` or `EXACT_ARRIVAL_UNSERVABLE`; see `RunStatus.error_code`), never by silently dropping the trip -- if the exact time and a window are both set; if no vehicle can reach it in time (the error names the earliest feasible time); or if it is reachable on its own but unsatisfiable with the rest of the problem (fleet size, capacity, competing pins). Read only when `timeSettings.timeUnit` is `clock`, as a day offset added to the matching `exactPickupTimeStr` time; ignored under `integer` or without that time, and a request that sets it where it would be ignored is refused with 400 `IGNORED_TIME_WINDOW`.",
            "type": "integer",
            "minimum": 0,
            "example": 1,
            "nullable": true,
            "default": null
          },
          "minDeliveryTimeInt": {
            "description": "An integer value representing the earliest time this trip can be delivered (time t=0 is the earliest start time). Leave empty if there are no time restrictions on delivery. **Read only when `timeSettings.timeUnit` is `integer`** (the default, including when `timeSettings` is omitted). Under `clock` it is ignored and `minDeliveryTimeStr` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "integer",
            "minimum": 0,
            "example": 20,
            "nullable": true,
            "default": null
          },
          "minDeliveryTimeStr": {
            "description": "A string value representing the earliest time this trip can be delivered. Leave empty if there are no time restrictions on delivery. The time format should correspond to the timeFormat field in timeSettings (at the top level of the problem, not inside problemData). **Read only when `timeSettings.timeUnit` is `clock`.** The default is `integer`, including when `timeSettings` is omitted, and then this field is ignored and `minDeliveryTimeInt` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "string",
            "example": "2024-01-01 12:30:00am",
            "nullable": true,
            "default": null
          },
          "minDeliveryDayInt": {
            "description": "An integer value representing the earliest day this trip can be delivered. Read only when `timeSettings.timeUnit` is `clock`, as a day offset added to the matching `minDeliveryTimeStr` time; ignored under `integer` or without that time, and a request that sets it where it would be ignored is refused with 400 `IGNORED_TIME_WINDOW`. Leave empty if there are no restrictions on delivery.",
            "type": "integer",
            "minimum": 0,
            "example": 1,
            "nullable": true,
            "default": null
          },
          "maxDeliveryTimeInt": {
            "description": "An integer value representing the latest time this trip can be delivered (time t=0 is the earliest start time). Leave empty if there are no time restrictions on delivery. **Read only when `timeSettings.timeUnit` is `integer`** (the default, including when `timeSettings` is omitted). Under `clock` it is ignored and `maxDeliveryTimeStr` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "integer",
            "minimum": 0,
            "example": 200,
            "nullable": true,
            "default": null
          },
          "maxDeliveryTimeStr": {
            "description": "A string value representing the latest time this trip can be delivered. Leave empty if there are no time restrictions on delivery. The time format should correspond to the timeFormat field in timeSettings (at the top level of the problem, not inside problemData). **Read only when `timeSettings.timeUnit` is `clock`.** The default is `integer`, including when `timeSettings` is omitted, and then this field is ignored and `maxDeliveryTimeInt` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "string",
            "example": "2024-01-01 11:59:59pm",
            "nullable": true,
            "default": null
          },
          "maxDeliveryDayInt": {
            "description": "An integer value representing the latest day this trip can be delivered. Read only when `timeSettings.timeUnit` is `clock`, as a day offset added to the matching `maxDeliveryTimeStr` time; ignored under `integer` or without that time, and a request that sets it where it would be ignored is refused with 400 `IGNORED_TIME_WINDOW`. Leave empty if there are no restrictions on delivery.",
            "type": "integer",
            "minimum": 0,
            "example": 1,
            "nullable": true,
            "default": null
          },
          "exactDeliveryTimeInt": {
            "description": "An integer value representing the exact time this trip must be delivered (time t=0 is the earliest start time). Leave empty if there are no time restrictions on delivery. This is a HARD PIN: the solver must deliver at exactly this time, applied as an equal min/max window. Set EITHER the exact* fields OR the min*/max* window for the same end, never both. These are checked when the run starts, not when it is submitted: `POST /problem` accepts the request (`202`), and the run then ends with `status: failed`, an `error` saying what to fix and an `error_code` (`EXACT_ARRIVAL_CONFLICT`, `EXACT_ARRIVAL_INFEASIBLE` or `EXACT_ARRIVAL_UNSERVABLE`; see `RunStatus.error_code`), never by silently dropping the trip -- if the exact time and a window are both set; if no vehicle can reach it in time (the error names the earliest feasible time); for an exact delivery, also if it is earlier than the trip's pickup (pinned or earliest possible) plus the pickup's service time plus the travel from pickup to delivery -- when both ends are pinned the error names both times and the travel time between them; or if it is reachable on its own but unsatisfiable with the rest of the problem (fleet size, capacity, competing pins). **Read only when `timeSettings.timeUnit` is `integer`** (the default, including when `timeSettings` is omitted). Under `clock` it is ignored and `exactDeliveryTimeStr` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "integer",
            "minimum": 0,
            "example": 50,
            "nullable": true,
            "default": null
          },
          "exactDeliveryTimeStr": {
            "description": "A string value representing the exact time this trip must be delivered. Leave empty if there are no time restrictions on delivery. The time format should correspond to the timeFormat field in timeSettings (at the top level of the problem, not inside problemData). This is a HARD PIN: the solver must deliver at exactly this time, applied as an equal min/max window. Set EITHER the exact* fields OR the min*/max* window for the same end, never both. These are checked when the run starts, not when it is submitted: `POST /problem` accepts the request (`202`), and the run then ends with `status: failed`, an `error` saying what to fix and an `error_code` (`EXACT_ARRIVAL_CONFLICT`, `EXACT_ARRIVAL_INFEASIBLE` or `EXACT_ARRIVAL_UNSERVABLE`; see `RunStatus.error_code`), never by silently dropping the trip -- if the exact time and a window are both set; if no vehicle can reach it in time (the error names the earliest feasible time); for an exact delivery, also if it is earlier than the trip's pickup (pinned or earliest possible) plus the pickup's service time plus the travel from pickup to delivery -- when both ends are pinned the error names both times and the travel time between them; or if it is reachable on its own but unsatisfiable with the rest of the problem (fleet size, capacity, competing pins). **Read only when `timeSettings.timeUnit` is `clock`.** The default is `integer`, including when `timeSettings` is omitted, and then this field is ignored and `exactDeliveryTimeInt` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "string",
            "example": "2024-01-01 01:00:00am",
            "nullable": true,
            "default": null
          },
          "exactDeliveryDayInt": {
            "description": "An integer value representing the exact day this trip must be delivered. Leave empty if there are no restrictions on delivery. This is a HARD PIN: the solver must deliver at exactly this time, applied as an equal min/max window. Set EITHER the exact* fields OR the min*/max* window for the same end, never both. These are checked when the run starts, not when it is submitted: `POST /problem` accepts the request (`202`), and the run then ends with `status: failed`, an `error` saying what to fix and an `error_code` (`EXACT_ARRIVAL_CONFLICT`, `EXACT_ARRIVAL_INFEASIBLE` or `EXACT_ARRIVAL_UNSERVABLE`; see `RunStatus.error_code`), never by silently dropping the trip -- if the exact time and a window are both set; if no vehicle can reach it in time (the error names the earliest feasible time); for an exact delivery, also if it is earlier than the trip's pickup (pinned or earliest possible) plus the pickup's service time plus the travel from pickup to delivery -- when both ends are pinned the error names both times and the travel time between them; or if it is reachable on its own but unsatisfiable with the rest of the problem (fleet size, capacity, competing pins). Read only when `timeSettings.timeUnit` is `clock`, as a day offset added to the matching `exactDeliveryTimeStr` time; ignored under `integer` or without that time, and a request that sets it where it would be ignored is refused with 400 `IGNORED_TIME_WINDOW`.",
            "type": "integer",
            "minimum": 0,
            "example": 1,
            "nullable": true,
            "default": null
          },
          "demand": {
            "description": "The amount of vehicle capacity this trip consumes.",
            "type": "integer",
            "example": 10,
            "minimum": 0,
            "default": 1
          },
          "excessTravelTime": {
            "description": "Maximum minutes the actual pickup->delivery transit may exceed the direct travel time for this trip (caps detours from pooling). Null = no cap.",
            "type": "number",
            "minimum": 0,
            "nullable": true,
            "example": 20
          },
          "pickupServiceDuration": {
            "description": "Minutes to service the pickup (e.g. park and load) at the trip's origin. Charged at the pickup stop. Defaults to 0.",
            "type": "number",
            "format": "float",
            "minimum": 0,
            "example": 3,
            "default": 0
          },
          "deliveryServiceDuration": {
            "description": "Minutes to service the drop-off (e.g. park and unload) at the trip's destination. Charged at the delivery stop. Defaults to 0.",
            "type": "number",
            "format": "float",
            "minimum": 0,
            "example": 2,
            "default": 0
          }
        }
      },
      "Vehicle": {
        "additionalProperties": false,
        "required": [
          "vehicleId",
          "startLocationId",
          "vehicleTypeId"
        ],
        "properties": {
          "vehicleId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "A unique identifier for this vehicle. Any non-empty string that is unique among your vehicles works — it is not required to be a UUID and the format is not validated. It is echoed back on each route stop as `vehicleId`."
          },
          "vehicleName": {
            "type": "string",
            "example": "Truck 1",
            "description": "A name for this vehicle (optional)."
          },
          "vehicleTypeId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "The vehicleTypeId for this vehicle. This vehicleTypeId must exist in the VehicleTypes passed in ProblemData for this problem."
          },
          "fixedCost": {
            "type": "number",
            "format": "float",
            "example": 100,
            "minimum": 0,
            "default": 0,
            "description": "The fixed cost for using this vehicle."
          },
          "minStartTimeInt": {
            "description": "An integer value representing the earliest time this vehicle can depart from its depot to begin its route(s) (time t=0 is the earliest start time). Leave empty if there are no restrictions on when the vehicle can start. **Read only when `timeSettings.timeUnit` is `integer`** (the default, including when `timeSettings` is omitted). Under `clock` it is ignored and `minStartTimeStr` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "integer",
            "minimum": 0,
            "example": 0,
            "nullable": true,
            "default": 0
          },
          "minStartTimeStr": {
            "description": "A string value representing the earliest time this vehicle can depart from its depot to begin its route(s). Leave empty if there are no restrictions on when the vehicle can start. The time format should match the timeFormat field in timeSettings (at the top level of the problem, not inside problemData). **Read only when `timeSettings.timeUnit` is `clock`.** The default is `integer`, including when `timeSettings` is omitted, and then this field is ignored and `minStartTimeInt` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "string",
            "example": "2024-01-01 12:00:00am",
            "nullable": true,
            "default": null
          },
          "minStartDayInt": {
            "description": "An integer value representing the earliest day this vehicle can depart from its depot to begin its route(s). Read only when `timeSettings.timeUnit` is `clock`, as a day offset added to the matching `minStartTimeStr` time; ignored under `integer` or without that time, and a request that sets it where it would be ignored is refused with 400 `IGNORED_TIME_WINDOW`. Leave empty if there are no restrictions on when the vehicle can start.",
            "type": "integer",
            "minimum": 0,
            "example": 0,
            "nullable": true,
            "default": 0
          },
          "maxEndTimeInt": {
            "description": "An integer value representing the latest time this vehicle must return to its depot after completing its route(s) (time t=0 is the earliest start time). Leave empty if there are no restrictions on when the vehicle must finish. **Read only when `timeSettings.timeUnit` is `integer`** (the default, including when `timeSettings` is omitted). Under `clock` it is ignored and `maxEndTimeStr` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "integer",
            "minimum": 0,
            "example": 100,
            "nullable": true,
            "default": null
          },
          "maxEndTimeStr": {
            "description": "A string value representing the latest time this vehicle must return to its depot after completing its route(s). Leave empty if there are no restrictions on when the vehicle must finish. The time format should match the timeFormat field in timeSettings (at the top level of the problem, not inside problemData). **Read only when `timeSettings.timeUnit` is `clock`.** The default is `integer`, including when `timeSettings` is omitted, and then this field is ignored and `maxEndTimeInt` is read instead; a request that sets only this form there is refused with 400 `IGNORED_TIME_WINDOW` rather than solved without it.",
            "type": "string",
            "example": "2024-01-01 11:59:59pm",
            "nullable": true,
            "default": null
          },
          "maxEndDayInt": {
            "description": "An integer value representing the latest day this vehicle must return to its depot after completing its route(s). Read only when `timeSettings.timeUnit` is `clock`, as a day offset added to the matching `maxEndTimeStr` time; ignored under `integer` or without that time, and a request that sets it where it would be ignored is refused with 400 `IGNORED_TIME_WINDOW`. Leave empty if there are no restrictions on when the vehicle must finish.",
            "type": "integer",
            "minimum": 0,
            "example": 1,
            "nullable": true,
            "default": 0
          },
          "startLocationId": {
            "type": "string",
            "description": "The locationId of the depot for this vehicle type. This locationId must exist in the Locations passed in ProblemData for this problem."
          },
          "endLocationId": {
            "type": "string",
            "default": null,
            "description": "The locationId of the depot for this vehicle type. If null, then the startLocationId will be used as the end location. This locationId must exist in the Locations passed in ProblemData for this problem."
          },
          "color": {
            "description": "The color to use for this vehicle in the output. Accepts a hex code (\"#FF0000\") or a CSS color name (\"crimson\").",
            "type": "string",
            "example": "#FF0000",
            "default": "#FFFFFF"
          }
        }
      },
      "VehicleType": {
        "additionalProperties": false,
        "required": [
          "vehicleTypeId"
        ],
        "properties": {
          "vehicleTypeId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "A unique identifier for this vehicle type."
          },
          "vehicleType": {
            "type": "string",
            "example": "Truck",
            "description": "A name for this vehicle type."
          },
          "capacity": {
            "type": "integer",
            "example": 10,
            "minimum": 0,
            "default": null,
            "description": "The maximum capacity of this vehicle type, in units of demand. Leave null if this vehicle type has no capacity limit."
          },
          "allowResupply": {
            "type": "boolean",
            "default": false,
            "description": "Whether this vehicle type can be resupplied during an optimization run."
          }
        }
      },
      "TravelCost": {
        "additionalProperties": false,
        "description": "This schema defines a travel cost between two locations. The travel cost can be used to specify the duration, distance, and cost of travel between two locations. An entry is required in this table for each pair of locations in the locations table.",
        "required": [
          "fromLocationId",
          "toLocationId"
        ],
        "properties": {
          "fromLocationId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "The locationId of the origin location for this travel cost. This locationId must exist in the Locations passed in ProblemData for this problem."
          },
          "toLocationId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440001",
            "description": "The locationId of the destination location for this travel cost. This locationId must exist in the Locations passed in ProblemData for this problem."
          },
          "vehicleTypeId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "The vehicleTypeId for this travel cost. This vehicleTypeId must exist in the VehicleTypes passed in ProblemData for this problem. If null, then the travel cost will apply to all vehicle types."
          },
          "vehicleType": {
            "type": "string",
            "example": "Truck",
            "description": "The name of the vehicle type for this travel cost. This field is optional and is provided for convenience only."
          },
          "duration": {
            "type": "number",
            "format": "float",
            "example": 15,
            "minimum": 0,
            "default": null,
            "nullable": true,
            "description": "The travel duration between these two locations, in the problem's time unit: **minutes** when `timeSettings.timeUnit` is `clock` (a value of 30 arrives half an hour later), and your own integer time unit when it is `integer`. The solver uses the number as-is and never rescales it, so a duration supplied in seconds under clock time makes every leg sixty times too long. `POST /cost_matrix` returns durations in minutes. If null, the travel duration will be calculated automatically based on the latitude and longitude of the locations."
          },
          "distance": {
            "type": "number",
            "format": "float",
            "example": 10,
            "minimum": 0,
            "default": null,
            "nullable": true,
            "description": "The travel distance between these two locations. Any unit can be used, as long as it is the same for every entry: the solver never converts it, and every distance in the results (`travelDistanceFromPreviousStop`, `totalDistanceTraveled`) is reported in this same unit — there is no distance-unit setting anywhere in the problem. A matrix from `POST /cost_matrix` is in **statute miles** for geographic (latitude/longitude) coordinates and in the coordinates' own unit for cartesian (x/y) ones. If null, the travel distance will be calculated automatically based on the latitude and longitude of the locations."
          },
          "cost": {
            "type": "number",
            "format": "float",
            "example": 1.5,
            "minimum": 0,
            "default": null,
            "nullable": true,
            "description": "The cost of travel between these two locations."
          }
        }
      },
      "RequestAccepted": {
        "type": "object",
        "description": "The body of the `202 Accepted` response to `POST /problem`. The run is queued and the solver runs in the background; poll for its status and then fetch results separately.",
        "properties": {
          "success": {
            "type": "boolean",
            "default": true,
            "example": true
          },
          "data": {
            "type": "object",
            "properties": {
              "run_id": {
                "type": "string",
                "example": "550e8400-e29b-41d4-a716-446655440000",
                "description": "The unique identifier for this optimization run. Store it: pass it as the `problemId` path parameter to `GET /problem/{problemId}` (or as `run_id` to `POST /run_status`) to poll, and to `POST /run_results_by_run_id` to fetch results once the run has succeeded. (`problemId` and `run_id` are the same value under two names.)"
              },
              "status": {
                "type": "string",
                "enum": [
                  "queued"
                ],
                "example": "queued",
                "description": "Always `queued` on acceptance."
              },
              "usage": {
                "$ref": "#/components/schemas/UsageSummary"
              }
            }
          }
        }
      },
      "UsageSummary": {
        "type": "object",
        "description": "This UTC month's optimization-task usage for the calling account. Present on every `202` from `POST /problem` for plans that include a monthly allowance, and returned by `GET /usage`. Absent from the `202` for plans that do not meter tasks (the web tiers).",
        "properties": {
          "tasksUsed": {
            "type": "integer",
            "example": 9840,
            "description": "Tasks counted against this month so far: stops or trips served by runs that reached `succeeded`, including any run that just succeeded. Not the run being submitted, whose tasks are only known when it succeeds."
          },
          "tasksIncluded": {
            "type": "integer",
            "nullable": true,
            "example": 10000,
            "description": "Tasks the plan includes per UTC month. `null` for a plan that does not meter tasks."
          },
          "period": {
            "type": "string",
            "example": "2026-09",
            "description": "The UTC calendar month these numbers are for, as `YYYY-MM`."
          },
          "resetsAt": {
            "type": "string",
            "format": "date-time",
            "example": "2026-10-01T00:00:00Z",
            "description": "When the allowance resets: the first instant of the next UTC month."
          },
          "plan": {
            "type": "string",
            "example": "developer",
            "description": "The plan tier the allowance comes from (as listed by `GET /plans`)."
          }
        }
      },
      "ProblemStatus": {
        "required": [
          "status"
        ],
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "description": "The solver's completion marker carried inside a results payload (`ProblemResult.problemStatus`). **It is always `completed`**: a results payload exists only for a run that finished, and the current solver writes no other value here (the other names in this enum are kept so old stored results still validate). This is **not** the value you poll for — the run lifecycle status returned by `GET /problem/{problemId}` and `POST /run_status` is `queued`/`running`/`succeeded`/`failed`/`cancelled` (see `RunStatus`); `succeeded` there and `completed` here describe the same finished run.",
            "example": "completed",
            "enum": [
              "failed",
              "completed",
              "running",
              "queued"
            ]
          }
        }
      },
      "RunStatus": {
        "description": "The run lifecycle status returned by `GET /problem/{problemId}` and `POST /run_status`. This is what a poller branches on. It is deliberately small — it never carries the routes; fetch those from `POST /run_results_by_run_id` once the run has succeeded.",
        "required": [
          "status"
        ],
        "type": "object",
        "properties": {
          "run_id": {
            "type": "string",
            "description": "The run this status is for."
          },
          "status": {
            "type": "string",
            "description": "A run starts `queued`, becomes `running`, and reaches exactly one terminal state: `succeeded`, `failed`, or `cancelled`. Poll until the status is terminal — there is no `completed` value here.",
            "example": "succeeded",
            "enum": [
              "queued",
              "running",
              "succeeded",
              "failed",
              "cancelled"
            ]
          },
          "progress": {
            "type": "number",
            "nullable": true,
            "description": "Always `null`. The key is kept so existing clients keep parsing; the solver does not report intermediate progress, and a progress bar cannot be built from it. Poll `status` instead."
          },
          "error": {
            "type": "string",
            "nullable": true,
            "description": "A human-readable reason, present only when `status` is `failed`. To act on a failure in code, branch on `error_code`; this wording may change."
          },
          "error_code": {
            "type": "string",
            "nullable": true,
            "description": "Why the run failed, as a stable code: present when `status` is `failed`, and `null` otherwise. **Branch on this, not on `error`** -- the sentence is written to be read and its wording may improve; the code will not change. These conditions are found while the run is being solved, after `POST /problem` has already answered `202`, so they never arrive as an HTTP error: poll for them here. One of: `NO_FEASIBLE_SOLUTION` (no plan satisfies the problem as specified -- conflicting time windows, capacities, vehicle shifts, or missing travel costs between stops that must reach each other), `SOLVE_TIMEOUT` (no solution was found within the maximum solve time), `INVERTED_TIME_WINDOW` (a location, trip or vehicle has a time window that ends before it starts), `INVALID_TRAVEL_COSTS` (specific pickup-delivery pairs have a missing or null travel cost), `EXACT_ARRIVAL_CONFLICT` (a stop or trip end sets an exact time AND a min/max window), `EXACT_ARRIVAL_INFEASIBLE` (an exact time no vehicle can make), `EXACT_ARRIVAL_UNSERVABLE` (exact times each reachable alone but unsatisfiable together), or `INTERNAL_SERVER_ERROR` -- the failure was ours, not your problem's; `error` still says what happened, and a code is always present so the absence of one never has to be read as meaning anything. A run that failed before this field existed has `error_code: null`.",
            "example": "NO_FEASIBLE_SOLUTION"
          },
          "run_datetime_unix": {
            "type": "number",
            "nullable": true,
            "description": "When the run was submitted, as a Unix timestamp."
          }
        }
      },
      "ProblemResult": {
        "description": "This schema defines the results of an optimization problem. The actual fields returned will depend on the type of optimization problem submitted.",
        "type": "object",
        "required": [
          "problemId"
        ],
        "properties": {
          "problemId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "The unique identifier for this optimization run."
          },
          "problemStatus": {
            "description": "The status of the optimization problem identified by the problemId parameter.",
            "$ref": "#/components/schemas/ProblemStatus"
          },
          "problemMetaData": {
            "description": "Metadata about the optimization problem",
            "$ref": "#/components/schemas/ProblemMetaData"
          },
          "routeStops": {
            "description": "A compact list of the stops visited, in order. **Not populated by the solver: expect `null`. Read the plan from `routeStopsFull` instead**, which is the populated, self-contained version. `routeStops` is retained for backward compatibility and may be absent or null even on a successful run.",
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/RouteStop"
            }
          },
          "routeStopsFull": {
            "description": "The optimized plan: every stop each vehicle visits, in order, with the full location and vehicle details attached. **This is the field to read for the route.** A run can succeed while assigning fewer than all of your stops (an over-constrained problem, or travel costs with null entries), so always check that every stop you submitted appears here (a depot appears only as a route's start and end), and treat `succeeded` with fewer stops served than submitted — or with all-zero distances — as an incomplete solve rather than a clean one. Unserved stops appear under `unassignedLocationsFull` (vehicle-routing) or `unassignedTripsFull` (pickup-delivery).",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RouteStopFull"
            }
          },
          "unassignedLocations": {
            "description": "**Not populated by the solver: expect `null`, even on a run that left a stop unassigned.** A `null` here says nothing about whether stops were dropped. Read `unassignedLocationsFull`, which is the list the solver fills. Retained for backward compatibility.",
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/UnassignedLocation"
            }
          },
          "unassignedLocationsFull": {
            "description": "A list of locations that were not visited by any vehicle during the optimization run, along with additional details about the location.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UnassignedLocationFull"
            }
          },
          "unassignedTrips": {
            "description": "**Not populated by the solver: expect `null`, even on a run that left a trip unassigned.** A `null` here says nothing about whether trips were dropped. Read `unassignedTripsFull`, which is the list the solver fills. Retained for backward compatibility.",
            "type": "array",
            "nullable": true,
            "items": {
              "$ref": "#/components/schemas/UnassignedTrip"
            }
          },
          "unassignedTripsFull": {
            "description": "A list of trips that were not serviced by any vehicle during the optimization run, along with additional details about the trip.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UnassignedTripFull"
            }
          },
          "paths": {
            "description": "Per-vehicle route geometry. Can be `null`. Each element's `path` field is a **JSON-encoded string** (not a nested object) — `JSON.parse` it before use.",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "vehicle_id": {
                  "type": "string",
                  "nullable": true,
                  "description": "The id of the vehicle this route geometry belongs to. Matches a `vehicleId` in your request; the map uses it to bind each path to its route."
                },
                "path": {
                  "type": "string",
                  "description": "A JSON-encoded string holding this vehicle's ordered route geometry. Parse it as JSON; it is not expanded inline."
                }
              }
            },
            "nullable": true,
            "default": []
          },
          "vehicleUsage": {
            "description": "A list of vehicle usage summaries for the optimization run.",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VehicleUsage"
            },
            "nullable": false,
            "default": []
          }
        }
      },
      "VehicleUsage": {
        "description": "Summarizes whether a vehicle was used during the optimization run and the fixed cost it incurred.",
        "type": "object",
        "required": [
          "vehicleId",
          "used",
          "fixedCostIncurred"
        ],
        "properties": {
          "vehicleId": {
            "type": "string",
            "description": "The unique identifier of the vehicle.",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "used": {
            "type": "boolean",
            "description": "Whether the vehicle was used (assigned at least one stop) during the optimization run.",
            "example": true
          },
          "fixedCostIncurred": {
            "type": "number",
            "format": "float",
            "description": "The fixed cost incurred by this vehicle. This equals the vehicle's fixed cost if the vehicle was used, otherwise 0.",
            "example": 100
          }
        }
      },
      "VehiclesAssigned": {
        "description": "Summary of vehicle assignment counts for an optimization run.",
        "type": "object",
        "properties": {
          "assigned": {
            "type": "integer",
            "example": 8,
            "description": "The number of vehicles assigned at least one stop."
          },
          "available": {
            "type": "integer",
            "example": 10,
            "description": "The number of vehicles supplied in the problem (the fleet size), whether or not they were used. A vehicle that restocks and makes several trips counts once."
          }
        }
      },
      "LocationsAssigned": {
        "description": "How many locations were served. `assigned == total` means nothing was left unassigned. Neither number counts depots, so neither is the number of locations you submitted.",
        "type": "object",
        "properties": {
          "assigned": {
            "type": "integer",
            "example": 45,
            "description": "The number of distinct locations visited as a stop on a vehicle's route. A route's start and end (the depot) are not counted."
          },
          "total": {
            "type": "integer",
            "example": 50,
            "description": "`assigned` plus everything left unassigned: the unassigned locations of a vehicle-routing problem, or the unassigned trips of a pickup-delivery problem. Depots are not counted, so this is smaller than the number of locations you submitted: a depot and two stops give `total: 2`. It is `null`, not `0`, only when the problem had no stops to visit (every location a depot), which `POST /problem` now refuses with `400 NO_STOPS_TO_VISIT`; you will see it only on runs submitted before that check."
          }
        }
      },
      "AssignmentSummary": {
        "description": "High-level counts of vehicles and locations that were assigned during the optimization run.",
        "type": "object",
        "properties": {
          "vehiclesAssigned": {
            "$ref": "#/components/schemas/VehiclesAssigned"
          },
          "locationsAssigned": {
            "$ref": "#/components/schemas/LocationsAssigned"
          }
        }
      },
      "RouteSummary": {
        "description": "Aggregate travel metrics across all vehicle routes in the optimization run.",
        "type": "object",
        "properties": {
          "totalDistanceTraveled": {
            "type": "number",
            "format": "float",
            "example": 5555,
            "description": "Total distance traveled across all vehicle routes, in the same unit as the `distance` values in the submitted `travelCosts` (there is no distance-unit setting; the solver reports what it was given). When the matrix came from `POST /cost_matrix` that is statute miles for geographic coordinates and the coordinates' own unit for cartesian."
          },
          "totalMinutesTraveled": {
            "type": "number",
            "format": "float",
            "example": 6232,
            "description": "Total travel time in minutes across all vehicle routes."
          },
          "totalTravelCost": {
            "type": "number",
            "format": "float",
            "example": 12023,
            "description": "Total travel cost across all vehicle routes."
          }
        }
      },
      "CostSummary": {
        "description": "Breakdown of fixed and variable costs for the optimization run.",
        "type": "object",
        "properties": {
          "fixedCost": {
            "type": "number",
            "format": "float",
            "example": 500,
            "description": "Sum of fixed costs for all vehicles used in the solution."
          },
          "travelCost": {
            "type": "number",
            "format": "float",
            "example": 12023,
            "description": "Sum of distance- or time-based travel costs across all routes."
          }
        }
      },
      "TimingSummary": {
        "description": "Timing information for the optimization run.",
        "type": "object",
        "properties": {
          "solveTime": {
            "type": "number",
            "format": "float",
            "example": 3.47,
            "description": "Wall-clock time in seconds taken by the solver to produce a solution."
          }
        }
      },
      "ResultSummary": {
        "description": "A high-level summary of the optimization result, covering assignments, route metrics, costs, and solve timing.",
        "type": "object",
        "properties": {
          "assignmentSummary": {
            "$ref": "#/components/schemas/AssignmentSummary"
          },
          "routeSummary": {
            "$ref": "#/components/schemas/RouteSummary"
          },
          "costSummary": {
            "$ref": "#/components/schemas/CostSummary"
          },
          "timingSummary": {
            "$ref": "#/components/schemas/TimingSummary"
          }
        }
      },
      "ProblemResponse": {
        "description": "The complete response for a solved optimization problem, combining the full result data with a high-level summary.",
        "type": "object",
        "required": [
          "problemResult"
        ],
        "properties": {
          "problemResult": {
            "description": "The full optimization result including routes, stops, and unassigned locations.",
            "$ref": "#/components/schemas/ProblemResult"
          },
          "resultSummary": {
            "description": "A high-level summary of key metrics from the optimization run.",
            "$ref": "#/components/schemas/ResultSummary"
          }
        }
      },
      "CostMatrixResponse": {
        "description": "The response schema for a cost matrix calculation request. This includes the travel costs and any errors encountered during the calculation.",
        "type": "object",
        "properties": {
          "travelCostResponses": {
            "description": "A list of travel costs between pairs of locations. Each travel cost represents the duration, distance, and cost of travel between two locations.",
            "$ref": "#/components/schemas/TravelCostResponses"
          },
          "travelCostErrors": {
            "description": "A list of errors encountered during the cost matrix calculation. Each error represents a problem with calculating the travel cost between two locations.",
            "$ref": "#/components/schemas/TravelCostErrors"
          }
        }
      },
      "TravelCostResponses": {
        "description": "This schema defines the response for travel costs between pairs of locations.",
        "type": "array",
        "items": {
          "type": "object",
          "required": [
            "fromLocationId",
            "toLocationId"
          ],
          "properties": {
            "fromLocationId": {
              "type": "string",
              "description": "The locationId of the origin location for this travel cost",
              "example": "550e8400-e29b-41d4-a716-446655440000"
            },
            "toLocationId": {
              "type": "string",
              "description": "The locationId of the destination location for this travel cost.",
              "example": "550e8400-e29b-41d4-a716-446655440001"
            },
            "fromLocationName": {
              "type": "string",
              "description": "The name of the origin location for this travel cost.",
              "example": "Chicago"
            },
            "toLocationName": {
              "type": "string",
              "description": "The name of the destination location for this travel cost.",
              "example": "Indianapolis"
            },
            "vehicleTypeId": {
              "type": "string",
              "description": "The vehicleTypeId for this travel cost. If null, then the travel cost will apply to all vehicle types.",
              "example": "550e8400-e29b-41d4-a716-446655440000"
            },
            "vehicleType": {
              "type": "string",
              "description": "The name of the vehicle type for this travel cost. If null, then the travel cost will apply to all vehicle types.",
              "example": "Truck"
            },
            "duration": {
              "type": "number",
              "format": "float",
              "description": "The travel duration between these two locations, in **minutes** (road-network durations are converted from seconds; the other methods derive minutes from the distance at `averageSpeed`, times `roadCircuityFactor`). Null when the pair could not be computed — see `status`.",
              "nullable": true
            },
            "distance": {
              "type": "number",
              "format": "float",
              "description": "The travel distance between these two locations: **statute miles** for geographic (latitude/longitude) coordinates, whatever unit the coordinates are in for cartesian (x/y) ones — no unit setting exists, and the solver later reports distances in this same unit. Null when the pair could not be computed — see `status`.",
              "nullable": true
            },
            "cost": {
              "type": "number",
              "format": "float",
              "description": "The cost of travel between these two locations.",
              "nullable": true
            },
            "status": {
              "type": "string",
              "enum": [
                "s",
                "e",
                "w"
              ],
              "description": "Whether this pair was computed. `s`: computed (this is also what the zero-cost self-pairs carry). `w`: the road network could not serve it — the two points are unreachable from each other by car, or the routing service was unavailable — and `duration`/`distance` are null with `errorMessage` saying which; the pair is **not** filled in by another method. `e`: a location is missing coordinates, so nothing could be computed for it. A null off-diagonal value in a submitted `travelCosts` matrix means \"unreachable\" to the solver; it is never treated as zero.",
              "example": "s",
              "default": "s"
            },
            "errorMessage": {
              "type": "string",
              "description": "If an error was encountered during the calculation of this travel cost, this field will contain a human-readable message describing the error. If no error was encountered, this field will be null.",
              "example": null,
              "default": null,
              "nullable": true
            }
          }
        }
      },
      "TravelCostErrors": {
        "description": "This schema defines an error encountered during the cost matrix calculation. Each error represents a problem with calculating the travel cost between two locations.",
        "type": "array",
        "items": {
          "type": "object",
          "required": [
            "errorMessage"
          ],
          "properties": {
            "errorMessage": {
              "type": "string",
              "description": "A human-readable message describing the error encountered during the cost matrix calculation.",
              "enum": [
                "Missing latitude or longitude for at least one location.",
                "Invalid vehicle type specified for at least one entry.",
                "Routing service unavailable.",
                "Unable to calculate travel cost between at least one pair of locations.",
                "Unknown error."
              ]
            },
            "errorDescription": {
              "type": "string",
              "example": "One or both locations are missing latitude or longitude.",
              "description": "A more detailed description of the error encountered during the cost matrix calculation.",
              "nullable": true
            }
          }
        }
      },
      "RouteStop": {
        "description": "A route stop represents a location that was visited by a vehicle during the optimization run.",
        "type": "object",
        "required": [
          "stopId",
          "locationId",
          "vehicleId",
          "serviceDuration",
          "stopNumber",
          "travelCostTo",
          "capacityServed"
        ],
        "properties": {
          "stopId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "A unique identifier for this stop, represented as a UUID."
          },
          "locationId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "The locationId of the stop."
          },
          "tripId": {
            "type": "string",
            "example": "1",
            "description": "The tripId of the trip that visited this stop, if applicable for this problem type."
          },
          "vehicleId": {
            "type": "string",
            "example": "660e8400-e29b-41d4-a716-446655440000",
            "description": "The vehicleId of the vehicle that visited this stop."
          },
          "arrivalTimeInt": {
            "type": "integer",
            "example": 10,
            "description": "The time at which the vehicle arrived at this stop, in integer time units from the start of the run. **Rounded to the nearest whole time unit.**",
            "nullable": true
          },
          "arrivalTimeStr": {
            "type": "string",
            "example": "2024-01-01 12:00:00am",
            "description": "The time at which the vehicle arrived at this stop, in the clock time format specified in the timeSettings.timeFormat field.",
            "nullable": true,
            "default": null
          },
          "departureTimeInt": {
            "type": "integer",
            "example": 12,
            "description": "The time at which the vehicle departed from this stop, in integer time units. This is the number of time units from the start of the optimization run.",
            "nullable": true,
            "default": null
          },
          "departureTimeStr": {
            "type": "string",
            "example": "2024-01-01 12:15:00am",
            "description": "The time at which the vehicle departed from this stop, in the clock time format specified in the timeSettings.timeFormat field.",
            "nullable": true,
            "default": null
          },
          "travelCostTo": {
            "type": "number",
            "format": "float",
            "example": 5,
            "deprecated": true,
            "description": "Deprecated: read `travelCostFromPreviousStop`. This holds the cost of the leg from this stop to the NEXT one (`0` on `route_end`), not the cost of reaching this stop, despite its name. It keeps that meaning so that results saved before `travelCostFromPreviousStop` existed still read the same."
          },
          "travelCostFromPreviousStop": {
            "type": "number",
            "format": "float",
            "example": 5,
            "description": "The cost of the leg from the previous stop to this stop (`0` on `route_start`), in the same units as `travelCostTo`. Sums to `routeSummary.totalTravelCost`. Absent on results saved before it existed: for those, shift `travelCostTo` one stop later within each route."
          },
          "waitDurationStart": {
            "type": "number",
            "format": "float",
            "example": 2.5,
            "minimum": 0,
            "default": 0,
            "description": "The amount of time the vehicle waited at this stop before servicing the location, in the problem's time unit (minutes under `timeSettings.timeUnit: clock`)."
          },
          "waitDurationEnd": {
            "type": "number",
            "format": "float",
            "example": 3,
            "minimum": 0,
            "default": 0,
            "description": "The amount of time the vehicle waited at this stop after servicing the location, in the problem's time unit (minutes under `timeSettings.timeUnit: clock`)."
          },
          "serviceDuration": {
            "type": "number",
            "format": "float",
            "example": 15.5,
            "minimum": 0,
            "default": 0,
            "description": "The amount of time the vehicle spent servicing this stop, in the problem's time unit (minutes under `timeSettings.timeUnit: clock`)."
          },
          "stopNumber": {
            "type": "integer",
            "example": 0,
            "description": "The position of this stop in its vehicle's route, **0-based**: index 0 is the vehicle's departure from its start depot (`route_start`), the first customer stop is 1, and the final return to the depot (`route_end`) is the last index. Consecutive within a vehicle's route; use it directly as an array index, never `stopNumber - 1`."
          },
          "capacityServed": {
            "type": "integer",
            "example": 10,
            "description": "The amount of capacity served at this stop."
          },
          "stopType": {
            "type": "string",
            "enum": [
              "location_visit",
              "pickup",
              "delivery",
              "route_start",
              "route_end",
              "break_start",
              "break_end",
              "restock",
              "drop"
            ],
            "example": "location_visit",
            "description": "The role this stop plays on the route: `location_visit` (a plain vehicle-routing visit), `pickup` and `delivery` (the two ends of a pickup-delivery trip), `route_start` and `route_end` (the depot legs that open and close a vehicle's route), `break_start` and `break_end` (a scheduled driver break), `restock` (a return to a depot to reload mid-route), and `drop` (a forced-unassigned stop the solver could not serve)."
          },
          "action": {
            "type": "string",
            "example": "Pickup 5 passengers at Chicago for Trip 1",
            "description": "The action taken at this stop. This is a human-readable description of the action taken at this stop, such as \"Pickup 5 passengers at Chicago for Trip 1\" or \"Deliver 10 packages to New York\"."
          },
          "travelDurationFromPreviousStop": {
            "type": "number",
            "format": "float",
            "example": 10,
            "description": "The travel duration from the previous stop to this stop, in the problem's time unit: minutes when `timeSettings.timeUnit` is `clock`, your integer time unit otherwise."
          },
          "travelDistanceFromPreviousStop": {
            "type": "number",
            "format": "float",
            "example": 5,
            "description": "The travel distance from the previous stop to this stop, in the same unit as the `distance` values in the submitted `travelCosts` (there is no distance-unit setting; the solver reports what it was given). When the matrix came from `POST /cost_matrix` that is statute miles for geographic coordinates and the coordinates' own unit for cartesian."
          }
        }
      },
      "RouteStopFull": {
        "description": "A route stop represents a location that was visited by a vehicle during the optimization run, along with additional details about the location and vehicle.",
        "allOf": [
          {
            "$ref": "#/components/schemas/RouteStop"
          },
          {
            "type": "object",
            "required": [
              "locationName",
              "color"
            ],
            "properties": {
              "locationName": {
                "type": "string",
                "example": "Chicago",
                "description": "The name of the location for this stop."
              },
              "color": {
                "type": "string",
                "example": "#FF0000",
                "description": "The color associated with this stop -- the serving vehicle's color, echoed back as supplied (hex code or CSS color name)."
              },
              "vehicleName": {
                "type": "string",
                "nullable": true,
                "example": "Truck 1",
                "description": "The name of the vehicle serving this stop, when the user provided one. Denormalized from the submitted vehicle (like locationName) so a route sheet is readable without re-joining the input; null when the vehicle is unnamed."
              },
              "fixedVehicleId": {
                "type": "string",
                "nullable": true,
                "example": "550e8400-e29b-41d4-a716-446655440000",
                "description": "The vehicleId this stop was pinned to via the input's fixedVehicleId, or null if it was not pinned. Lets a client tell 'this vehicle was chosen by the solver' from 'this vehicle was required', without re-joining against the request."
              }
            }
          }
        ]
      },
      "UnassignedLocation": {
        "description": "An unassigned object represents a location that was not visited by any vehicle during the optimization run.",
        "type": "object",
        "required": [
          "locationId",
          "demandDropped"
        ],
        "properties": {
          "locationId": {
            "type": "string",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "description": "The locationId of the unassigned location."
          },
          "demandDropped": {
            "type": "integer",
            "example": 10,
            "minimum": 0,
            "description": "The amount of demand that was not served at this location."
          }
        }
      },
      "UnassignedLocationFull": {
        "description": "An unassigned object represents a location that was not visited by any vehicle during the optimization run, along with additional details about the location.",
        "allOf": [
          {
            "$ref": "#/components/schemas/UnassignedLocation"
          },
          {
            "type": "object",
            "required": [
              "locationName",
              "color"
            ],
            "properties": {
              "locationName": {
                "type": "string",
                "example": "Chicago",
                "description": "The name of the unassigned location."
              },
              "latitude": {
                "type": "number",
                "format": "float",
                "example": 39.791,
                "description": "The latitude of the unassigned location."
              },
              "longitude": {
                "type": "number",
                "format": "float",
                "example": -89.398,
                "description": "The longitude of the unassigned location."
              },
              "isDepot": {
                "type": "boolean",
                "default": false,
                "description": "Whether this unassigned location serves only as a depot (true) or if this location should be serviced (false)."
              }
            }
          }
        ]
      },
      "UnassignedTrip": {
        "description": "An unassigned trip represents a trip that was not serviced by any vehicle during the optimization run.",
        "type": "object",
        "required": [
          "tripId",
          "demandDropped",
          "capacityServed"
        ],
        "properties": {
          "tripId": {
            "type": "string",
            "example": "1",
            "description": "The tripId of the unassigned trip."
          },
          "demandDropped": {
            "type": "integer",
            "example": 10,
            "minimum": 0,
            "description": "The amount of demand that was not able to be served by any vehicle for this trip."
          },
          "capacityServed": {
            "type": "integer",
            "example": 0,
            "minimum": 0,
            "description": "The amount of capacity that was served for this dropped trip. This will typically be 0, but in some cases (e.g. partial deliveries) it may be greater than 0."
          }
        }
      },
      "UnassignedTripFull": {
        "description": "An unassigned trip represents a trip that was not serviced by any vehicle during the optimization run, along with additional details about the trip.",
        "allOf": [
          {
            "$ref": "#/components/schemas/UnassignedTrip"
          },
          {
            "type": "object",
            "required": [
              "tripName",
              "fromLocationName",
              "fromLocationId",
              "toLocationName",
              "toLocationId",
              "fromLatitude",
              "fromLongitude",
              "toLatitude",
              "toLongitude"
            ],
            "properties": {
              "tripName": {
                "type": "string",
                "example": "Trip 1",
                "description": "The name of the unassigned trip."
              },
              "fromLocationName": {
                "type": "string",
                "example": "Chicago",
                "description": "The name of the origin location for this trip."
              },
              "toLocationName": {
                "type": "string",
                "example": "Indianapolis",
                "description": "The name of the destination location for this trip."
              },
              "fromLocationId": {
                "type": "string",
                "example": "550e8400-e29b-41d4-a716-446655440000",
                "description": "The locationId of the origin location for this trip."
              },
              "toLocationId": {
                "type": "string",
                "example": "550e8400-e29b-41d4-a716-446655440001",
                "description": "The locationId of the destination location for this trip."
              },
              "fromLatitude": {
                "type": "number",
                "format": "float",
                "example": 39.791,
                "description": "The latitude of the origin location for this trip."
              },
              "fromLongitude": {
                "type": "number",
                "format": "float",
                "example": -89.398,
                "description": "The longitude of the origin location for this trip."
              },
              "toLatitude": {
                "type": "number",
                "format": "float",
                "example": 39.768,
                "description": "The latitude of the destination location for this trip."
              },
              "toLongitude": {
                "type": "number",
                "format": "float",
                "example": -86.158,
                "description": "The longitude of the destination location for this trip."
              }
            }
          }
        ]
      },
      "ProblemMetaData": {
        "description": "Metadata about the optimization problem, such as the problem type and the time settings used for the optimization run.",
        "type": "object",
        "required": [
          "problemType",
          "submissionDate",
          "queueTime",
          "submissionTimestamp"
        ],
        "properties": {
          "problemType": {
            "type": "string",
            "enum": [
              "pickup-delivery",
              "vehicle-routing"
            ],
            "example": "vehicle-routing",
            "description": "The type of optimization problem that was run."
          },
          "submissionTimestamp": {
            "type": "integer",
            "format": "int64",
            "description": "The date and time when the optimization problem was submitted, in unix timestamp in seconds",
            "example": 1704067200
          },
          "queueTime": {
            "type": "number",
            "format": "float",
            "example": 5,
            "description": "The time spent in the queue before the optimization run started, in seconds."
          },
          "optimizationTime": {
            "type": "number",
            "format": "float",
            "example": 120,
            "description": "The time taken to run the optimization, in seconds."
          }
        }
      },
      "CostMatrixRequest": {
        "additionalProperties": false,
        "description": "This schema defines the request body for calculating a cost matrix. It includes the locations and travel costs to use for calculating the cost matrix.",
        "type": "object",
        "required": [
          "locations",
          "costSettings"
        ],
        "properties": {
          "locations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Location"
            },
            "description": "The locations to use for calculating the cost matrix."
          },
          "travelCosts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TravelCost"
            },
            "description": "The travel costs to use for calculating the cost matrix. If not provided, the cost matrix will be calculated automatically based on the latitude and longitude of the locations. A partial cost matrix can be provided to avoid recalculating travel durations and distances between locations."
          },
          "costSettings": {
            "$ref": "#/components/schemas/CostSettings",
            "description": "The settings to use for calculating travel costs between locations. This includes the cost metric (duration or distance), cost multiplier, cost adder, and methods for calculating distances and durations.",
            "nullable": false
          },
          "coordinateSystem": {
            "$ref": "#/components/schemas/CoordinateSystem"
          }
        }
      },
      "CostSettings": {
        "additionalProperties": false,
        "description": "This schema defines the settings to use for calculating travel costs between locations. It includes the cost metric (duration or distance), cost multiplier, cost adder, and methods for calculating distances and durations.",
        "required": [
          "costMetric"
        ],
        "properties": {
          "costMetric": {
            "type": "string",
            "enum": [
              "duration",
              "distance",
              "custom"
            ],
            "example": "duration",
            "description": "**Required** — there is no default; a `costSettings` without a `costMetric` is rejected with 422. The metric used as the base for the travel cost, before any multiplier or adder. `duration` bases cost on travel time, `distance` on travel distance, and `custom` uses the cost values you supply directly in `travelCosts`."
          },
          "costMultiplier": {
            "$ref": "#/components/schemas/CostMultiplier",
            "description": "The cost multiplier to apply to the base cost metric (duration or distance) to calculate the final travel cost."
          },
          "costAdder": {
            "$ref": "#/components/schemas/CostAdder",
            "description": "The cost adder to apply to the base cost metric (duration or distance) to calculate the final travel cost."
          },
          "autoCalculateDuration": {
            "type": "boolean",
            "description": "Whether `POST /cost_matrix` should compute travel durations from each location's coordinates. **When omitted, it defaults to `true` if you supply no `travelCosts`, and to `false` if you do** — so your own matrix is never silently recomputed. An explicit `null` is rejected (send `true`/`false` or omit it). The road network (`driving`) is used when you name `driving` as the `durationMethod`, or when an omitted `durationMethod` inherits `driving` from a `driving` `distanceMethod` because you explicitly sent `autoCalculateDuration: true`; the default method (`geo`) never calls the road network."
          },
          "autoCalculateDistance": {
            "type": "boolean",
            "description": "Whether `POST /cost_matrix` should compute travel distances from each location's coordinates. **When omitted, it defaults to `true` if you supply no `travelCosts`, and to `false` if you do** — so your own matrix is never silently recomputed. An explicit `null` is rejected (send `true`/`false` or omit it). The road network (`driving`) is used only when you name `driving` as the `distanceMethod`; the default method (`geo`) never calls the road network."
          },
          "distanceMethod": {
            "type": "string",
            "enum": [
              "driving",
              "geo",
              "manhattan",
              "euclidean"
            ],
            "default": "geo",
            "description": "The method for calculating distances. `geo` (the default) is the ellipsoidal geodesic distance on the WGS84 ellipsoid, reported in **statute miles**; `driving` uses OpenRouteService for real road distances. `euclidean` is the planar straight-line distance and `manhattan` the right-angle (grid) distance. **`euclidean` is accepted only for cartesian (x/y) coordinates**: on geographic coordinates it would treat degrees as planar units, so the request is refused with `400 INVALID_METHOD_FOR_COORDINATE_SYSTEM` — the straight-line method on the globe is `geo`. `manhattan` works for both: on the plane it is |dx| + |dy| in the coordinates' unit; on geographic coordinates it is the sum of the two geodesic legs (the north–south and east–west separations), in statute miles. `driving` is likewise geographic-only."
          },
          "durationMethod": {
            "type": "string",
            "enum": [
              "driving",
              "geo",
              "manhattan",
              "euclidean"
            ],
            "description": "The method to use for calculating durations between locations. `geo` derives a duration from the ellipsoidal geodesic (WGS84) distance; `driving` uses OpenRouteService for real road durations; `manhattan`/`euclidean` derive it from the planar (cartesian) or, for geographic coordinates, geodesic distance. **There is no fixed default — if you omit `durationMethod` it is inherited from `distanceMethod`** so the two stay consistent (do not rely on a `geo` default: an SDK that fills one would break a cartesian request). An omitted `durationMethod` inherits `driving` from `distanceMethod` only when you explicitly send `autoCalculateDuration: true`; otherwise it falls back to `geo`."
          },
          "averageSpeed": {
            "type": "number",
            "default": 30,
            "nullable": true,
            "description": "Average travel speed used to turn a computed DISTANCE into a DURATION for the non-driving methods (geo, manhattan, euclidean), in distance units per hour. Ignored when durationMethod is \"driving\", where the road network supplies real durations.\n\nThis exists because a straight-line distance is not a time. Before it, geo durations were the distance number itself, which meant every vehicle travelled at exactly 60 mph between adjacent stops. The default of 30 suits urban and suburban work; raise it for long inter-city legs, lower it for dense city centres or mountain roads.",
            "example": 30
          },
          "roadCircuityFactor": {
            "type": "number",
            "default": 1.25,
            "nullable": true,
            "description": "Multiplier applied to a straight-line distance when deriving DURATION, to account for roads not running as the crow flies. Affects duration only - the reported distance stays the true geodesic value the method name promises. 1.25 is a common urban rule of thumb; use 1.0 to model travel that really is direct.",
            "example": 1.25
          }
        }
      },
      "CostMultiplier": {
        "additionalProperties": false,
        "description": "This schema defines a cost multiplier to apply to the base cost metric (duration or distance) to calculate the final travel cost.",
        "type": "object",
        "required": [
          "vehicleTypeDependence"
        ],
        "properties": {
          "vehicleTypeDependence": {
            "type": "boolean",
            "default": false,
            "description": "Whether the cost multiplier should depend on the vehicle type. If true, then the cost multiplier will be applied separately for each vehicle type. If false, then the cost multiplier will be applied based on the first vehicle type in the VehicleTypes list."
          },
          "baseMultiplier": {
            "type": "number",
            "format": "float",
            "example": 1.5,
            "default": 1,
            "description": "The multiplier to apply to the base cost metric (duration or distance) to calculate the final travel cost."
          },
          "vehicleTypeMultipliers": {
            "type": "array",
            "items": {
              "additionalProperties": false,
              "type": "object",
              "properties": {
                "vehicleTypeId": {
                  "type": "string",
                  "example": "550e8400-e29b-41d4-a716-446655440000",
                  "description": "The vehicleTypeId for which this cost multiplier applies. If not provided, then the cost multiplier will apply to all vehicle types."
                },
                "multiplier": {
                  "type": "number",
                  "format": "float",
                  "example": 1.5,
                  "default": 1,
                  "description": "The multiplier to apply to the base cost metric (duration or distance) for this vehicle type."
                }
              }
            }
          }
        }
      },
      "CostAdder": {
        "additionalProperties": false,
        "description": "This schema defines a cost adder to apply to the base cost metric (duration or distance) to calculate the final travel cost.",
        "type": "object",
        "required": [
          "vehicleTypeDependence"
        ],
        "properties": {
          "vehicleTypeDependence": {
            "type": "boolean",
            "default": false,
            "description": "Whether the cost adder should depend on the vehicle type. If true, then the cost adder will be applied separately for each vehicle type. If false, then the cost adder will be applied based on the first vehicle type in the VehicleTypes list."
          },
          "baseAdder": {
            "type": "number",
            "format": "float",
            "example": 10,
            "default": 0,
            "description": "The adder to apply to the base cost metric (duration or distance) to calculate the final travel cost."
          },
          "vehicleTypeAdders": {
            "type": "array",
            "items": {
              "additionalProperties": false,
              "type": "object",
              "properties": {
                "vehicleTypeId": {
                  "type": "string",
                  "example": "550e8400-e29b-41d4-a716-446655440000",
                  "description": "The vehicleTypeId for which this cost adder applies. If not provided, then the cost adder will apply to all vehicle types."
                },
                "adder": {
                  "type": "number",
                  "format": "float",
                  "example": 10,
                  "default": 0,
                  "description": "The adder to apply to the base cost metric (duration or distance) for this vehicle type."
                }
              }
            }
          }
        }
      },
      "TableLimit": {
        "type": "object",
        "required": [
          "maxRows"
        ],
        "properties": {
          "maxRows": {
            "type": "integer",
            "description": "Maximum number of rows allowed for this table"
          }
        }
      },
      "PlanLimits": {
        "type": "object",
        "required": [
          "locations",
          "trips",
          "vehicles",
          "vehicle_types"
        ],
        "properties": {
          "locations": {
            "$ref": "#/components/schemas/TableLimit",
            "description": "Limits for locations table"
          },
          "trips": {
            "$ref": "#/components/schemas/TableLimit",
            "description": "Limits for trips table"
          },
          "vehicles": {
            "$ref": "#/components/schemas/TableLimit",
            "description": "Limits for vehicles table"
          },
          "vehicle_types": {
            "$ref": "#/components/schemas/TableLimit",
            "description": "Limits for vehicle types table"
          },
          "saved_scenarios": {
            "type": "integer",
            "nullable": true,
            "description": "Maximum saved scenarios (null for unlimited)",
            "default": null
          },
          "run_history_days": {
            "type": "integer",
            "nullable": true,
            "description": "Days of run history retention (null for unlimited)",
            "default": null
          },
          "max_concurrent_solves": {
            "type": "integer",
            "description": "Maximum number of solves (queued + running) the user may have in flight at once",
            "default": 2
          },
          "monthly_included_tasks": {
            "type": "integer",
            "nullable": true,
            "description": "Optimization tasks included per month on this plan (null for tiers that do not meter tasks, i.e. the web tiers). A billable task is one stop or trip served by a SUCCEEDED solve; unassigned stops, and failed or cancelled runs, cost nothing."
          }
        }
      },
      "PublicPlan": {
        "type": "object",
        "description": "A public, subscribable plan tier returned by GET /plans. The single source of truth for pricing/entitlement limits so the front end never restates them.",
        "required": [
          "tier"
        ],
        "properties": {
          "tier": {
            "type": "string",
            "description": "Stable internal tier key (never changes), e.g. \"free\" or \"professional\".",
            "example": "professional"
          },
          "planName": {
            "type": "string",
            "nullable": true,
            "description": "Customer-facing display name as configured in the Clerk dashboard, e.g. \"Professional\".",
            "example": "Professional"
          },
          "limits": {
            "$ref": "#/components/schemas/PlanLimits",
            "description": "Resolved per-tier limits."
          },
          "features": {
            "type": "object",
            "additionalProperties": {
              "type": "boolean"
            },
            "description": "Capability flags keyed by feature; absent keys default to allowed."
          }
        }
      },
      "ExampleScenarioCard": {
        "type": "object",
        "description": "A published example scenario as a lightweight card for the gallery. Load the full scenario with `GET /example_scenario/{slug}`, using `slug`.",
        "required": [
          "instance_id",
          "slug"
        ],
        "properties": {
          "instance_id": {
            "type": "string",
            "description": "Unique id of the stored scenario behind this example. To load the example, use `slug` with `GET /example_scenario/{slug}`."
          },
          "slug": {
            "type": "string",
            "description": "Stable, URL-safe unique key ([a-z0-9-])."
          },
          "scenario_name": {
            "type": "string",
            "description": "Full display name of the scenario."
          },
          "model_type": {
            "type": "string",
            "description": "Model type, e.g. \"pickup-delivery\" or \"vehicle-routing\"."
          },
          "title": {
            "type": "string",
            "description": "Short card heading (may be empty)."
          },
          "brief_description": {
            "type": "string",
            "description": "1-3 sentence blurb shown under the title."
          },
          "banner_image": {
            "type": "string",
            "description": "Card image URL; empty when none is set."
          },
          "constraints": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Key constraints shown as bullets."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags for gallery filtering and card chips."
          },
          "summary": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Small stat objects, e.g. [{\"Locations\":17},{\"Trips\":12}]."
          },
          "industry": {
            "type": "string",
            "description": "Industry/vertical for gallery filtering, e.g. \"healthcare\", \"transportation\" (empty when not set)."
          },
          "secondary_industries": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Additional industries this example also belongs to, beside the primary `industry`. A gallery filter should treat an example as matching if the requested industry is the primary OR appears here. Optional and additive: an absent or empty array behaves exactly as a card with only `industry` set, so clients that ignore it are unaffected. Exists because some scenarios genuinely sit in two verticals - special-needs school transport is education and healthcare; roll-off container swaps are public-works and logistics - and forcing one label makes an industry page look emptier than the library actually is.",
            "examples": [
              [
                "transportation"
              ]
            ]
          },
          "geography": {
            "$ref": "#/components/schemas/ExampleGeography"
          },
          "scale": {
            "type": "string",
            "description": "Derived instance size bucket: \"small\", \"medium\", or \"large\" (empty when not set)."
          },
          "constraints_tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Constraint-shape tags for gallery filtering (e.g. \"time windows\", \"vehicle types\"), distinct from `industry`."
          },
          "colorDimension": {
            "type": "string",
            "enum": [
              "vehicle_eligibility",
              "time_of_day",
              "demand",
              "none"
            ],
            "description": "Which fact this example's per-location colours encode. The same light-to-dark ramp means time of day on one example and demand on another, so a legend must read this rather than infer meaning from the swatches. \"none\" means the example's stops are uniform and are deliberately left uncoloured.",
            "example": "vehicle_eligibility"
          },
          "colorDimensionLabel": {
            "type": "string",
            "nullable": true,
            "description": "Display name for colorDimension, rendered verbatim as a legend title. Null when colorDimension is \"none\".",
            "example": "Vehicle eligibility"
          },
          "colorScale": {
            "type": "string",
            "enum": [
              "categorical",
              "ramp"
            ],
            "nullable": true,
            "description": "How to render the legend: \"categorical\" for a swatch per group, \"ramp\" for an ordered light-to-dark strip. Null when colorDimension is \"none\".",
            "example": "categorical"
          },
          "colorBuckets": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Only the colours this example actually uses, in scale order, so a legend shows the two steps in play rather than every step of the scale. Empty when colorDimension is \"none\".",
            "example": [
              "slategray",
              "darkkhaki"
            ]
          }
        }
      },
      "ExampleGeography": {
        "type": "object",
        "description": "Where an example scenario is set, for gallery filtering.",
        "properties": {
          "city": {
            "type": "string"
          },
          "region": {
            "type": "string"
          },
          "country": {
            "type": "string"
          }
        }
      },
      "ScenarioTables": {
        "type": "object",
        "description": "Canonical tables of a persisted scenario (the input_object 'data' block). Rows use the canonical component-model fields (canonical names/casing) only; the front end derives display/working fields, geoJson, and denormalized coords on load. Trips reference locations by fromLocationId/toLocationId (no coordinates).",
        "properties": {
          "locations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Location"
            }
          },
          "trips": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Trip"
            }
          },
          "vehicles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Vehicle"
            }
          },
          "vehicle_types": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VehicleType"
            }
          },
          "travel_costs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TravelCost"
            }
          },
          "route_stops": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RouteStop"
            }
          }
        }
      },
      "CoordinateSystem": {
        "type": "string",
        "enum": [
          "geographic",
          "cartesian"
        ],
        "default": "geographic",
        "description": "Which coordinate family this problem's locations use. `geographic` (the default, and what every existing payload means) uses latitude/longitude on the Earth. `cartesian` uses unbounded x/y on an arbitrary plane, for scenarios with positions but no addresses. It describes the DATA, not the costing, so it is set once per request rather than per cost setting - and it decides which distance/duration methods are valid."
      }
    },
    "securitySchemes": {
      "api_key": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your Optimiciti API key, sent as `Authorization: Bearer <api key>`. Required on every operation marked with this scheme when calling from a script or server. The key decides whose account the call acts for; a `user_id` / `userId` in the body never does."
      }
    },
    "responses": {
      "GatewayUnauthorized": {
        "description": "No API key, or an invalid one. Sent by the gateway before the request reaches the API.",
        "content": {
          "application/problem+json": {
            "schema": {
              "type": "object",
              "required": [
                "type",
                "title",
                "status"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "example": "https://httpproblems.com/http-status/401"
                },
                "title": {
                  "type": "string",
                  "example": "Unauthorized"
                },
                "status": {
                  "type": "integer",
                  "example": 401
                },
                "detail": {
                  "type": "string"
                },
                "instance": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "GatewayRateLimited": {
        "description": "Rate limit exceeded for this endpoint. Wait `retry-after` seconds and retry.",
        "headers": {
          "retry-after": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer",
              "example": 60
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "type": "object",
              "required": [
                "type",
                "title",
                "status"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "example": "https://httpproblems.com/http-status/429"
                },
                "title": {
                  "type": "string",
                  "example": "Too Many Requests"
                },
                "status": {
                  "type": "integer",
                  "example": 429
                },
                "detail": {
                  "type": "string"
                },
                "instance": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "api_key": []
    }
  ]
}
