{"version":"1.0.0","lang":"en","api_base":"https://runner.cargoduo.com/v1","docs_base":"https://runner.cargoduo.com/api/public/api-docs","agent_prompt":"Integrate the CargoDuo packing API into this project. Fetch https://runner.cargoduo.com/api/public/api-docs/llms-full.txt and follow it.","files":[{"name":"llms.txt","url":"https://runner.cargoduo.com/api/public/api-docs/llms.txt","description":"Index of every page, for language models."},{"name":"llms-full.txt","url":"https://runner.cargoduo.com/api/public/api-docs/llms-full.txt","description":"Integration playbook for coding agents."},{"name":"openapi.json","url":"https://runner.cargoduo.com/api/public/api-docs/openapi.json","description":"OpenAPI 3.1 document."}],"sections":[{"id":"Get started","title":"Get started"},{"id":"API reference","title":"API reference"},{"id":"AI tools","title":"AI tools"}],"groups":[{"id":"Pack","title":"Pack"},{"id":"Jobs","title":"Jobs"},{"id":"Projects","title":"Projects"},{"id":"Vehicles","title":"Vehicles"},{"id":"Templates","title":"Templates"},{"id":"Account","title":"Account"}],"pages":[{"slug":"introduction","title":"Introduction","section":"Get started","summary":"Plan how cargo fits into trucks, trailers, containers and pallets with one API call.","keywords":"overview base url agent prompt how it works enterprise","url":"https://runner.cargoduo.com/api/public/api-docs/pages/introduction.md","markdown":"# Introduction\n\nPlan how cargo fits into trucks, trailers, containers and pallets with one API call.\n\nSend the vehicles you have and the items you ship. Get back a 3D load plan: which vehicle each item goes in, its position and orientation, the loading order and a PDF link.\n\nThe API comes with the Pro and Enterprise plans.\n\n## Set up your coding agent\n\nPaste this into Claude Code, Cursor, Codex or any coding agent:\n\n```prompt\nIntegrate the CargoDuo packing API into this project. Fetch https://runner.cargoduo.com/api/public/api-docs/llms-full.txt and follow it.\n```\n\n## How it works\n\n| Step | Call | You get |\n|---|---|---|\n| 1 | `POST /v1/pack` | A job: validated, priced and queued |\n| 2 | `GET /v1/jobs/{id}` | Its status, until `succeeded` (or pass `?wait=60`) |\n| 3 | `GET /v1/jobs/{id}/result` | Placements, loading steps and `pdf_url` |\n\n## Base URL\n\n```text\nhttps://runner.cargoduo.com/v1\n```\n\nLengths in cm, weights in kg, 1 credit = 1 USD.\n\n## Next steps\n\n- [Quickstart](https://runner.cargoduo.com/api/public/api-docs/pages/quickstart.md): Create a key, send one request, read the plan.\n- [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md): API keys, scopes and IP allowlists.\n- [Create a packing job](https://runner.cargoduo.com/api/public/api-docs/pages/create-pack-job.md): Validates the request, reserves the worst-case price from your credits and queues a job.\n- [Results \u0026 coordinates](https://runner.cargoduo.com/api/public/api-docs/pages/results.md): Where every item sits, in which orientation and loading order.\n"},{"slug":"quickstart","title":"Quickstart","section":"Get started","summary":"Create a key, send one request, read the plan.","keywords":"first request example curl node python wait poll","related":["create-pack-job","results","idempotency"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/quickstart.md","markdown":"# Quickstart\n\nCreate a key, send one request, read the plan.\n\n## 1. Create an API key\n\nIn the developer console, open **API keys** and create a key. The secret is shown once.\n\n```bash\nexport CARGODUO_API_KEY=\"cd_live_...\"\n```\n\n## 2. Create a job\n\n`?wait=60` answers with the finished job when it ends within 60 s.\n\n```bash\ncurl -X POST \"https://runner.cargoduo.com/v1/pack?wait=60\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\" \\\n  -H \"Idempotency-Key: order-1042-v1\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"reference\": \"order-1042\",\n    \"vehicles\": [\n      {\n        \"id\": \"trailer\",\n        \"type\": \"trailer\",\n        \"name\": \"13.6 m trailer\",\n        \"length\": 1360,\n        \"width\": 245,\n        \"height\": 270,\n        \"max_weight\": 24000\n      }\n    ],\n    \"items\": [\n      {\n        \"id\": \"pallet-eu\",\n        \"label\": \"Euro pallet\",\n        \"length\": 120,\n        \"width\": 80,\n        \"height\": 140,\n        \"weight\": 450,\n        \"quantity\": 18,\n        \"stackable\": false\n      },\n      {\n        \"id\": \"carton-a\",\n        \"label\": \"Carton A\",\n        \"length\": 60,\n        \"width\": 40,\n        \"height\": 40,\n        \"weight\": 12,\n        \"quantity\": 40,\n        \"rotation\": \"upright\"\n      }\n    ],\n    \"options\": {\n      \"packing_mode\": \"auto\"\n    }\n  }'\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/pack?wait=60\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n    \"Idempotency-Key\": \"order-1042-v1\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"reference\": \"order-1042\",\n    \"vehicles\": [\n      {\n        \"id\": \"trailer\",\n        \"type\": \"trailer\",\n        \"name\": \"13.6 m trailer\",\n        \"length\": 1360,\n        \"width\": 245,\n        \"height\": 270,\n        \"max_weight\": 24000\n      }\n    ],\n    \"items\": [\n      {\n        \"id\": \"pallet-eu\",\n        \"label\": \"Euro pallet\",\n        \"length\": 120,\n        \"width\": 80,\n        \"height\": 140,\n        \"weight\": 450,\n        \"quantity\": 18,\n        \"stackable\": false\n      },\n      {\n        \"id\": \"carton-a\",\n        \"label\": \"Carton A\",\n        \"length\": 60,\n        \"width\": 40,\n        \"height\": 40,\n        \"weight\": 12,\n        \"quantity\": 40,\n        \"rotation\": \"upright\"\n      }\n    ],\n    \"options\": {\n      \"packing_mode\": \"auto\"\n    }\n  }),\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.post(\n    \"https://runner.cargoduo.com/v1/pack\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\", \"Idempotency-Key\": \"order-1042-v1\"},\n    params={\n        \"wait\": 60,\n    },\n    json={\n        \"reference\": \"order-1042\",\n        \"vehicles\": [\n            {\n                \"id\": \"trailer\",\n                \"type\": \"trailer\",\n                \"name\": \"13.6 m trailer\",\n                \"length\": 1360,\n                \"width\": 245,\n                \"height\": 270,\n                \"max_weight\": 24000,\n            },\n        ],\n        \"items\": [\n            {\n                \"id\": \"pallet-eu\",\n                \"label\": \"Euro pallet\",\n                \"length\": 120,\n                \"width\": 80,\n                \"height\": 140,\n                \"weight\": 450,\n                \"quantity\": 18,\n                \"stackable\": False,\n            },\n            {\n                \"id\": \"carton-a\",\n                \"label\": \"Carton A\",\n                \"length\": 60,\n                \"width\": 40,\n                \"height\": 40,\n                \"weight\": 12,\n                \"quantity\": 40,\n                \"rotation\": \"upright\",\n            },\n        ],\n        \"options\": {\n            \"packing_mode\": \"auto\",\n        },\n    },\n)\nprint(response.status_code, response.json())\n```\n\nFinished in time: `200`, `status: \"succeeded\"`, the plan in `result`. Still running: `202`, go to step 3.\n\n## 3. Poll until it ends\n\n```bash\ncurl \"https://runner.cargoduo.com/v1/jobs/$JOB_ID?include=result\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nasync function waitForJob(job) {\n  while (job.status === \"queued\" || job.status === \"running\") {\n    await new Promise((r) =\u003e setTimeout(r, 2000));\n    const res = await fetch(`https://runner.cargoduo.com/v1/jobs/${job.id}?include=result`, {\n      headers: { Authorization: `Bearer ${process.env.CARGODUO_API_KEY}` },\n    });\n    job = await res.json();\n  }\n  return job;\n}\n```\n\n```python\nimport os, time, requests\n\ndef wait_for_job(job):\n    headers = {\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"}\n    while job[\"status\"] in (\"queued\", \"running\"):\n        time.sleep(2)\n        job = requests.get(f\"https://runner.cargoduo.com/v1/jobs/{job['id']}\", params={\"include\": \"result\"}, headers=headers).json()\n    return job\n```\n\n## 4. Read the plan\n\n| Field | Holds |\n|---|---|\n| `result.vehicles[].placements[]` | Position, size, orientation and loading `step` of every item |\n| `result.unplaced[]` | Items that did not fit, with a `reason` |\n| `cost.credits` | What the job was charged |\n| `pdf_url` | A link that builds the loading plan PDF |\n\n\u003e [!TIP]\n\u003e `POST /v1/estimate` validates and prices the same body for free.\n\n## Related\n\n- [Create a packing job](https://runner.cargoduo.com/api/public/api-docs/pages/create-pack-job.md): Validates the request, reserves the worst-case price from your credits and queues a job.\n- [Results \u0026 coordinates](https://runner.cargoduo.com/api/public/api-docs/pages/results.md): Where every item sits, in which orientation and loading order.\n- [Idempotency](https://runner.cargoduo.com/api/public/api-docs/pages/idempotency.md): Retry POST /v1/pack without creating a second job.\n"},{"slug":"authentication","title":"Authentication","section":"Get started","summary":"API keys, scopes and IP allowlists.","keywords":"api key bearer token scopes ip allowlist rotate revoke expire 401 403","related":["errors","limits"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md","markdown":"# Authentication\n\nAPI keys, scopes and IP allowlists.\n\nSend an API key as a bearer token on every `/v1` request.\n\n```bash\ncurl https://runner.cargoduo.com/v1/balance \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n## API keys\n\n| Key | Detail |\n|---|---|\n| Format | `cd_live_` + 40 characters |\n| Created by | The organization owner, in the developer console |\n| Shown | Once, at creation. Only a hash is stored |\n| Per organization | Up to 20 active keys |\n| Optional | Expiry date, IP allowlist (IPs or CIDR ranges) |\n| Rotate | New secret, same settings. The old one stops at once |\n\n\u003e [!WARNING]\n\u003e Keep keys on your server. Never ship one in a browser or mobile app.\n\n## Scopes\n\n| Scope | Endpoints |\n|---|---|\n| `pack:write` | `POST /v1/pack`, `POST /v1/estimate`, `DELETE /v1/jobs/{id}`, `POST /v1/projects/{id}/pack` |\n| `jobs:read` | `GET /v1/jobs`, `GET /v1/jobs/{id}`, `GET /v1/jobs/{id}/result` |\n| `account:read` | `GET /v1/limits`, `GET /v1/balance`, `GET /v1/usage` |\n| `resources:read` | `GET /v1/projects`, `GET /v1/projects/{id}`, `GET /v1/vehicles`, `GET /v1/vehicles/{id}`, `GET /v1/templates`, `GET /v1/templates/{id}` |\n| `resources:write` | `POST /v1/projects`, `PATCH /v1/projects/{id}`, `DELETE /v1/projects/{id}`, `POST /v1/projects/{id}/pack`, `POST /v1/vehicles`, `PATCH /v1/vehicles/{id}`, `DELETE /v1/vehicles/{id}`, `POST /v1/templates`, `PATCH /v1/templates/{id}`, `DELETE /v1/templates/{id}` |\n\nNew keys get every scope. `POST /v1/projects/{id}/pack` needs both `pack:write` and `resources:write`.\n\n## Errors\n\n| Status | Code | Meaning |\n|---|---|---|\n| 401 | `unauthorized` | Missing, malformed or unknown API key. |\n| 401 | `key_revoked` | The key was revoked. |\n| 401 | `key_expired` | The key passed its expiry date. |\n| 403 | `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| 403 | `subscription_inactive` | The organization has no active subscription that includes the API. |\n| 403 | `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| 403 | `scope_missing` | The key lacks a scope this endpoint needs. |\n| 429 | `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n## Related\n\n- [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md): One error envelope, stable codes, clear retry rules.\n- [Rate limits \u0026 limits](https://runner.cargoduo.com/api/public/api-docs/pages/limits.md): Request rates, job size, concurrency and retention.\n"},{"slug":"errors","title":"Errors","section":"Get started","summary":"One error envelope, stable codes, clear retry rules.","keywords":"error codes envelope details request id retry 400 401 402 403 404 409 410 422 429 500 503 validation","related":["limits","idempotency"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/errors.md","markdown":"# Errors\n\nOne error envelope, stable codes, clear retry rules.\n\nEvery non-2xx response has one shape. Branch on `error.code`, never on `message`.\n\n```json\n{\n  \"error\": {\n    \"code\": \"validation_failed\",\n    \"message\": \"The request is invalid.\",\n    \"request_id\": \"req_8fK2Lq9ZtX4mN7bV1cW3yH6p\",\n    \"details\": [\n      {\n        \"path\": \"items[3].length\",\n        \"code\": \"out_of_range\",\n        \"message\": \"length must be greater than 0.\"\n      }\n    ]\n  }\n}\n```\n\n| Field | Holds |\n|---|---|\n| `code` | Stable, snake_case |\n| `message` | English, for logs |\n| `request_id` | Also in `X-Request-Id`. Quote it to support |\n| `details[]` | On 400/422: `path`, `code`, `message` per problem |\n\n## Codes\n\n| Status | Code | Meaning | What to do | Retry |\n|---|---|---|---|---|\n| 400 | `invalid_json` | The body is not valid JSON or a field has the wrong type. | Fix the request body. | No |\n| 401 | `unauthorized` | Missing, malformed or unknown API key. | Send Authorization: Bearer cd_live_…; check the key was copied completely. | No |\n| 401 | `key_revoked` | The key was revoked. | Create a new key in the developer console. | No |\n| 401 | `key_expired` | The key passed its expiry date. | Create a new key in the developer console. | No |\n| 402 | `insufficient_credits` | Available credits do not cover the smallest possible run of this job. | Buy a credit pack or wait for the monthly credits; nothing was charged. | No |\n| 403 | `ip_not_allowed` | The calling IP is not on the key's allowlist. | Call from an allowed IP or edit the allowlist. | No |\n| 403 | `subscription_inactive` | The organization has no active subscription that includes the API. | Renew the subscription. | No |\n| 403 | `account_inactive` | The organization is suspended, or has no owner whose account is active. | Contact support. | No |\n| 403 | `scope_missing` | The key lacks a scope this endpoint needs. | Use a key with the scope (pack:write, jobs:read, account:read, resources:read, resources:write). | No |\n| 403 | `preset_read_only` | Presets cannot be changed or deleted. | Create your own vehicle with POST /v1/vehicles. | No |\n| 403 | `plan_limit_reached` | The organization's plan allows no more projects or cargo items. | Delete projects you no longer need, or move to a larger plan. | No |\n| 404 | `not_found` | No /v1 endpoint has this method and path. | Check the method and path against the endpoint list. | No |\n| 404 | `job_not_found` | No job with this id in your organization. | Check the id. | No |\n| 404 | `vehicle_not_found` | No vehicle with this id among the presets or your organization's vehicles. | Check the id against GET /v1/vehicles. | No |\n| 404 | `template_not_found` | No template with this id in your organization. | Check the id against GET /v1/templates. | No |\n| 404 | `project_not_found` | No project with this id in your organization. | Check the id against GET /v1/projects. | No |\n| 409 | `job_not_finished` | The job is still queued or running. | Poll GET /v1/jobs/{id} until `status` is `succeeded`. | Yes |\n| 409 | `project_busy` | The project is being packed right now (in the app or by another job). | Wait for that run to end, then retry. | Yes |\n| 409 | `job_failed` | The job failed; it has no result. | Read `job.error`; failures on our side are not charged (time_limit_exceeded is charged like a canceled job) — resubmit if the cause was temporary. | No |\n| 409 | `job_canceled` | The job was canceled; it has no result. | Resubmit if needed. | No |\n| 409 | `job_already_finished` | The job already ended and cannot be canceled. | Nothing to do. | No |\n| 400 | `idempotency_key_invalid` | The Idempotency-Key header is longer than 128 characters. | Use a shorter key (a UUID works). | No |\n| 410 | `result_expired` | The result passed the retention window and was deleted. | Resubmit the job if you need the plan again. | No |\n| 413 | `payload_too_large` | The request body exceeds 10 MB. | Send fewer or simpler items. | No |\n| 422 | `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. | Fix each details[].path; validation failures are free. | No |\n| 429 | `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. | Wait Retry-After seconds, then retry with backoff. | Yes |\n| 429 | `too_many_jobs` | The organization already has the maximum number of queued + running jobs. | Wait for running jobs to finish, then retry. | Yes |\n| 500 | `internal_error` | Something failed on our side. | Retry with backoff; quote request_id if it persists. | Yes |\n| 503 | `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. | Retry later. | Yes |\n\n## Validation details\n\n`details[].code` on `422 validation_failed`:\n\n| Code | Meaning |\n|---|---|\n| `required` | A required field is missing |\n| `invalid_value` | Wrong type or not one of the allowed values |\n| `out_of_range` | Too small, too large or too long |\n| `duplicate_id` | An id is used twice |\n| `unknown_vehicle` | No preset or vehicle of yours with this `vehicle_id` |\n| `unknown_template` | No template of yours with this `template_id` |\n| `type_not_allowed` | Vehicle type not enabled for your organization |\n| `unknown_group` | An item names a group that is not listed |\n| `does_not_fit` | The item fits no listed vehicle |\n| `too_many_items` | More item units than the job limit |\n| `too_many_vehicles` | More vehicles than the job limit |\n| `unknown_field` | A field the API does not know |\n\n## Job errors\n\nA `failed` or `canceled` job says why in `job.error.code`:\n\n| Code | Meaning |\n|---|---|\n| `canceled` | Canceled by DELETE /v1/jobs/{id}. Charged base + compute used. |\n| `time_limit_exceeded` | The compute budget ran out before any vehicle was packed. Charged like a canceled job: per-job fee, large-load surcharge and the compute used, never the per-vehicle fee. |\n| `engine_failed` | The packing engine failed. Not charged. |\n| `queue_timeout` | The job waited too long for a compute slot. Not charged. |\n| `server_restarted` | The service restarted while the job was queued or running. Not charged. |\n\n## Retries\n\nRetry `429`, `500` and `503` with exponential backoff, honouring `Retry-After`. Send the same `Idempotency-Key` ([Idempotency](https://runner.cargoduo.com/api/public/api-docs/pages/idempotency.md)). Never retry other 4xx unchanged.\n\n## Related\n\n- [Rate limits \u0026 limits](https://runner.cargoduo.com/api/public/api-docs/pages/limits.md): Request rates, job size, concurrency and retention.\n- [Idempotency](https://runner.cargoduo.com/api/public/api-docs/pages/idempotency.md): Retry POST /v1/pack without creating a second job.\n"},{"slug":"limits","title":"Rate limits \u0026 limits","section":"Get started","summary":"Request rates, job size, concurrency and retention.","keywords":"rate limit 429 retry-after concurrency queue max items vehicles retention headers","related":["get-limits","errors"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/limits.md","markdown":"# Rate limits \u0026 limits\n\nRequest rates, job size, concurrency and retention.\n\nDefaults below. `GET /v1/limits` returns the values for your organization.\n\n| Limit | Value |\n|---|---|\n| Requests per minute / hour / day | 60 / 1,000 / 5,000 |\n| Item units per job (sum of `quantity`) | 2,000 |\n| Vehicles per job (incl. added ones) | 20 |\n| Compute budget per job | 90 s |\n| Jobs computing at once | 1 |\n| Jobs queued + running | 20 |\n| Vehicle types | `truck`, `trailer`, `container`, `pallet` |\n| Request body | 10 MB |\n| `?wait` | 60 s |\n| Retention of bodies, results, PDF links | 14 days |\n\n## Rate limits\n\nCounted per minute, hour and day, for each key and for the organization. The strictest wins.\n\n| Header | Meaning |\n|---|---|\n| `X-RateLimit-Limit` | Limit of the tightest window |\n| `X-RateLimit-Remaining` | Requests left in it |\n| `X-RateLimit-Reset` | Unix time it resets |\n| `Retry-After` | On 429: seconds to wait |\n\nOver the limit: `429 rate_limited`. More than 60 failed authentications per minute from one IP also answer `429`.\n\n## Jobs\n\nJobs over the concurrency limit wait as `queued`. Past 20 queued + running jobs, `POST /v1/pack` answers `429 too_many_jobs`.\n\n## Retention\n\nRequest and result bodies, and PDF links, are deleted after 14 days (`expires_at` on every job). Status, counts and cost are kept.\n\n## Related\n\n- [Get limits](https://runner.cargoduo.com/api/public/api-docs/pages/get-limits.md): The limits that apply to your organization and the rate card new jobs are priced with.\n- [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md): One error envelope, stable codes, clear retry rules.\n"},{"slug":"credits","title":"Credits \u0026 pricing","section":"Get started","summary":"What a job costs and where credits come from.","keywords":"price cost billing balance credits plan purchased reserve 402 estimate","related":["estimate-pack-job","get-balance"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/credits.md","markdown":"# Credits \u0026 pricing\n\nWhat a job costs and where credits come from.\n\nBalances and costs are in US dollars: 1 credit = $1. Fields keep the name `credits`. Most jobs cost **0.27–0.34 credits**.\n\n- **Plan credits**: 50 per seat each billing month. Spent first, expire at month end.\n- **Purchased credits**: bought in the console. Never expire.\n\n## What a job costs\n\n| Outcome | Charged |\n|---|---|\n| Succeeded | Per job, per vehicle loaded, compute time |\n| Canceled | Per job and compute used. No per-vehicle charge |\n| `time_limit_exceeded` | Like a canceled job |\n| Failed on our side | Nothing |\n| Rejected (any 4xx) | Nothing |\n| Vehicle, template and project calls | Nothing |\n\n`POST /v1/estimate` prices a request for free. `GET /v1/limits` returns the live rate card.\n\n## Balance\n\n- A job reserves its worst-case price, then pays the actual price when it ends.\n- With no available credits, `POST /v1/pack` answers `402 insufficient_credits`.\n- `GET /v1/balance` and the `X-Credits-Remaining` header show what is left.\n\n## Related\n\n- [Estimate a packing job](https://runner.cargoduo.com/api/public/api-docs/pages/estimate-pack-job.md): Runs the same validation as POST /v1/pack and returns the price range, without creating a job or reserving credits.\n- [Get the balance](https://runner.cargoduo.com/api/public/api-docs/pages/get-balance.md): Available, plan, purchased and reserved credits.\n"},{"slug":"idempotency","title":"Idempotency","section":"Get started","summary":"Retry POST /v1/pack without creating a second job.","keywords":"idempotency key retry duplicate timeout replay","related":["create-pack-job","errors"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/idempotency.md","markdown":"# Idempotency\n\nRetry POST /v1/pack without creating a second job.\n\nSend an `Idempotency-Key` with `POST /v1/pack` (or `POST /v1/projects/{id}/pack`). Retry with the same key and you get the same job, charged once.\n\n```bash\ncurl -X POST https://runner.cargoduo.com/v1/pack \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\" \\\n  -H \"Idempotency-Key: order-1042-v1\" \\\n  -H \"Content-Type: application/json\" \\\n  -d @request.json\n```\n\n| Rule | Detail |\n|---|---|\n| Window | 24 hours, per organization |\n| Key | Any string up to 128 characters: a UUID or your order id |\n| Replay | The original job, with `Idempotent-Replayed: true`. `200` once it ended, `202` before |\n| Too long | `400 idempotency_key_invalid` |\n\n\u003e [!NOTE]\n\u003e One key per logical request. A changed body needs a new key.\n\n## Related\n\n- [Create a packing job](https://runner.cargoduo.com/api/public/api-docs/pages/create-pack-job.md): Validates the request, reserves the worst-case price from your credits and queues a job.\n- [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md): One error envelope, stable codes, clear retry rules.\n"},{"slug":"results","title":"Results \u0026 coordinates","section":"Get started","summary":"Where every item sits, in which orientation and loading order.","keywords":"result placements coordinates origin x y z axis orientation step loading order unplaced centre of gravity axle","related":["get-job-result","pdf-reports"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/results.md","markdown":"# Results \u0026 coordinates\n\nWhere every item sits, in which orientation and loading order.\n\nAll positions and sizes are in cm, measured from the min corner of the cargo space.\n\n## Coordinates\n\n| Axis | Runs along | 0 is at |\n|---|---|---|\n| `x` | Length | The front wall. `x = length` is the door |\n| `y` | Height | The floor |\n| `z` | Width | The right wall, seen from the door looking in |\n\n```text coordinates\n            front wall  (x = 0)\n   z = width ┌────────────────┐ z = 0   ← origin (0, 0, 0)\n             │                │\n   left wall │      x ↓       │ right wall\n             │                │\n             └────────────────┘\n                door  (x = length)\n         top view, standing at the door\n```\n\n## Placements\n\n`result.vehicles[].placements[]`, one per item unit:\n\n| Field | Holds |\n|---|---|\n| `item_id`, `instance` | Your item id and which unit of its `quantity` (1…n) |\n| `x`, `y`, `z` | Min corner of the item as placed |\n| `length`, `width`, `height` | Size along x, z and y as placed |\n| `orientation`, `rotated` | How it was turned (below) |\n| `step` | Loading order in the vehicle, 1 first |\n\n```json\n{\n  \"item_id\": \"pallet-eu\",\n  \"instance\": 2,\n  \"label\": \"Euro pallet\",\n  \"x\": 0,\n  \"y\": 0,\n  \"z\": 120,\n  \"length\": 80,\n  \"width\": 120,\n  \"height\": 140,\n  \"rotated\": true,\n  \"orientation\": \"wlh\",\n  \"step\": 2,\n  \"weight\": 450\n}\n```\n\nEuro pallet 2 stands at the front wall (`x` 0–80), on the floor, `z` 120–240 from the right wall, turned a quarter.\n\n## Orientation\n\nWhich of your sides lies along x, z and y:\n\n| Code | Meaning |\n|---|---|\n| `lwh` | As defined |\n| `wlh` | Turned 90° around the vertical axis |\n| `lhw`, `hlw` | Laid on its side |\n| `hwl`, `whl` | Stood on its end |\n\n`rotation: \"upright\"` allows `lwh` and `wlh` only. `fixed` allows `lwh` only.\n\n## Per vehicle\n\n| Field | Holds |\n|---|---|\n| `items_placed`, `total_weight` | What is in it |\n| `volume_utilization`, `weight_utilization` | 0–1 |\n| `center_of_gravity` | x, y, z of the load |\n| `axle_loads[]` | Load per axle. Requires access (`axle_loads`) |\n\n## Not loaded\n\n`result.unplaced[]` lists units that did not fit, with a `reason`. With `packing_mode: \"auto\"` vehicles are added (up to the job limit) before anything is left out.\n\n## Related\n\n- [Get a job result](https://runner.cargoduo.com/api/public/api-docs/pages/get-job-result.md): The load plan of a succeeded job: every vehicle used, every placement, what did not fit and the PDF link.\n- [PDF reports](https://runner.cargoduo.com/api/public/api-docs/pages/pdf-reports.md): A shareable link that builds the loading plan PDF.\n"},{"slug":"pdf-reports","title":"PDF reports","section":"Get started","summary":"A shareable link that builds the loading plan PDF.","keywords":"pdf report pdf_url share link loading plan print driver warehouse expires","related":["results","get-job"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/pdf-reports.md","markdown":"# PDF reports\n\nA shareable link that builds the loading plan PDF.\n\nEvery succeeded job has a `pdf_url`. Open it in a browser to get the loading plan PDF: 3D views and step-by-step loading, in the viewer's language and units.\n\n```text\nhttps://app.cargoduo.com/r/Xq7Lm2Vt9KbR4cN8wZp1Hs6Yd3Fg5Jk0Ta2Ue7Wn4Ci\n```\n\n| Link | Detail |\n|---|---|\n| Who can open it | Anyone with the link. Share it with the warehouse or the driver |\n| Valid until | The job's `expires_at` (14 days) |\n| Stored | Nothing. The PDF is built in the viewer's browser |\n| Empty | On failed, canceled and expired jobs |\n\n\u003e [!NOTE]\n\u003e Treat the link like a password: it shows the whole plan.\n\n## Related\n\n- [Results \u0026 coordinates](https://runner.cargoduo.com/api/public/api-docs/pages/results.md): Where every item sits, in which orientation and loading order.\n- [Get a job](https://runner.cargoduo.com/api/public/api-docs/pages/get-job.md): A job's status, progress, summary and cost.\n"},{"slug":"saved-data","title":"Saved data \u0026 projects","section":"Get started","summary":"Vehicles, templates and projects shared with the app.","keywords":"vehicles templates projects fleet saved vehicle_id template_id created_by members app 3d editor crud","related":["list-vehicles","list-templates","pack-project"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/saved-data.md","markdown":"# Saved data \u0026 projects\n\nVehicles, templates and projects shared with the app.\n\nYour organization's vehicles, cargo templates and projects are the same records the CargoDuo app shows. Change one here and it changes there.\n\n| Resource | Ids | Endpoints |\n|---|---|---|\n| Vehicles | Presets: slug (`standard-trailer-13-6m`), yours: `veh_…` | `/v1/vehicles` (presets are read-only) |\n| Templates | `tpl_…` | `/v1/templates` |\n| Projects | `prj_…`; slots `pv_…`, items `item_…` | `/v1/projects` |\n\nReading needs `resources:read`; creating, changing and deleting need `resources:write`. These calls cost nothing and count against the rate limits like any other.\n\n## Use them in a job\n\n```json\n{\n  \"vehicles\": [\n    {\n      \"vehicle_id\": \"veh_118\",\n      \"quantity\": 2\n    }\n  ],\n  \"items\": [\n    {\n      \"template_id\": \"tpl_42\",\n      \"quantity\": 18\n    },\n    {\n      \"template_id\": \"tpl_43\",\n      \"quantity\": 40,\n      \"weight\": 14\n    }\n  ]\n}\n```\n\nFields sent next to an id override the saved values. `id` defaults to the saved id.\n\n## Projects\n\n| Step | Call |\n|---|---|\n| 1 | `POST /v1/projects` with a name, vehicles (`vehicle_id`, `quantity`) and items |\n| 2 | `POST /v1/projects/{id}/pack`: priced and run like `POST /v1/pack` |\n| 3 | The plan is saved into the project and opens in the app (`app_url`) |\n\nIf someone edits the project while the job runs, the plan stays on the job only (warning `project_not_saved`).\n\n## Members\n\nEach record shows `created_by` and `updated_by`. A call counts as the member who created the key. Filter lists with `?created_by=me` or a member id (`usr_…`).\n\n## Related\n\n- [List vehicles](https://runner.cargoduo.com/api/public/api-docs/pages/list-vehicles.md): Every vehicle you can name with vehicle_id: the presets (containers, trailers, trucks, pallets of the types your organization may use), then your organization's own vehicles.\n- [List templates](https://runner.cargoduo.com/api/public/api-docs/pages/list-templates.md): Your organization's cargo templates (saved items), newest first.\n- [Pack a project](https://runner.cargoduo.com/api/public/api-docs/pages/pack-project.md): Packs the project's vehicles and items exactly like POST /v1/pack: same price, credit reservation, limits, Idempotency-Key, ?wait= and job.\n"},{"slug":"api-reference","title":"Overview","section":"API reference","summary":"Base URL, conventions and every endpoint.","keywords":"endpoints overview base url headers conventions openapi","related":["authentication","errors","limits"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/api-reference.md","markdown":"# Overview\n\nBase URL, conventions and every endpoint.\n\n```text\nhttps://runner.cargoduo.com/v1\n```\n\nJSON over HTTPS. Lengths in cm, weights in kg, money in credits.\n\n## Endpoints\n\n### Pack\n\n| Endpoint | Page | Scope |\n|---|---|---|\n| `POST /v1/pack` | [Create a packing job](https://runner.cargoduo.com/api/public/api-docs/pages/create-pack-job.md) | `pack:write` |\n| `POST /v1/estimate` | [Estimate a packing job](https://runner.cargoduo.com/api/public/api-docs/pages/estimate-pack-job.md) | `pack:write` |\n\n### Jobs\n\n| Endpoint | Page | Scope |\n|---|---|---|\n| `GET /v1/jobs` | [List jobs](https://runner.cargoduo.com/api/public/api-docs/pages/list-jobs.md) | `jobs:read` |\n| `GET /v1/jobs/{id}` | [Get a job](https://runner.cargoduo.com/api/public/api-docs/pages/get-job.md) | `jobs:read` |\n| `GET /v1/jobs/{id}/result` | [Get a job result](https://runner.cargoduo.com/api/public/api-docs/pages/get-job-result.md) | `jobs:read` |\n| `DELETE /v1/jobs/{id}` | [Cancel a job](https://runner.cargoduo.com/api/public/api-docs/pages/cancel-job.md) | `pack:write` |\n\n### Projects\n\n| Endpoint | Page | Scope |\n|---|---|---|\n| `GET /v1/projects` | [List projects](https://runner.cargoduo.com/api/public/api-docs/pages/list-projects.md) | `resources:read` |\n| `POST /v1/projects` | [Create a project](https://runner.cargoduo.com/api/public/api-docs/pages/create-project.md) | `resources:write` |\n| `GET /v1/projects/{id}` | [Get a project](https://runner.cargoduo.com/api/public/api-docs/pages/get-project.md) | `resources:read` |\n| `PATCH /v1/projects/{id}` | [Update a project](https://runner.cargoduo.com/api/public/api-docs/pages/update-project.md) | `resources:write` |\n| `DELETE /v1/projects/{id}` | [Delete a project](https://runner.cargoduo.com/api/public/api-docs/pages/delete-project.md) | `resources:write` |\n| `POST /v1/projects/{id}/pack` | [Pack a project](https://runner.cargoduo.com/api/public/api-docs/pages/pack-project.md) | `pack:write` and `resources:write` |\n\n### Vehicles\n\n| Endpoint | Page | Scope |\n|---|---|---|\n| `GET /v1/vehicles` | [List vehicles](https://runner.cargoduo.com/api/public/api-docs/pages/list-vehicles.md) | `resources:read` |\n| `GET /v1/vehicles/{id}` | [Get a vehicle](https://runner.cargoduo.com/api/public/api-docs/pages/get-vehicle.md) | `resources:read` |\n| `POST /v1/vehicles` | [Create a vehicle](https://runner.cargoduo.com/api/public/api-docs/pages/create-vehicle.md) | `resources:write` |\n| `PATCH /v1/vehicles/{id}` | [Update a vehicle](https://runner.cargoduo.com/api/public/api-docs/pages/update-vehicle.md) | `resources:write` |\n| `DELETE /v1/vehicles/{id}` | [Delete a vehicle](https://runner.cargoduo.com/api/public/api-docs/pages/delete-vehicle.md) | `resources:write` |\n\n### Templates\n\n| Endpoint | Page | Scope |\n|---|---|---|\n| `GET /v1/templates` | [List templates](https://runner.cargoduo.com/api/public/api-docs/pages/list-templates.md) | `resources:read` |\n| `GET /v1/templates/{id}` | [Get a template](https://runner.cargoduo.com/api/public/api-docs/pages/get-template.md) | `resources:read` |\n| `POST /v1/templates` | [Create a template](https://runner.cargoduo.com/api/public/api-docs/pages/create-template.md) | `resources:write` |\n| `PATCH /v1/templates/{id}` | [Update a template](https://runner.cargoduo.com/api/public/api-docs/pages/update-template.md) | `resources:write` |\n| `DELETE /v1/templates/{id}` | [Delete a template](https://runner.cargoduo.com/api/public/api-docs/pages/delete-template.md) | `resources:write` |\n\n### Account\n\n| Endpoint | Page | Scope |\n|---|---|---|\n| `GET /v1/limits` | [Get limits](https://runner.cargoduo.com/api/public/api-docs/pages/get-limits.md) | `account:read` |\n| `GET /v1/balance` | [Get the balance](https://runner.cargoduo.com/api/public/api-docs/pages/get-balance.md) | `account:read` |\n| `GET /v1/usage` | [Get usage](https://runner.cargoduo.com/api/public/api-docs/pages/get-usage.md) | `account:read` |\n\n## Headers\n\n| Header | Direction | Meaning |\n|---|---|---|\n| `Authorization` | Request | `Bearer cd_live_…` |\n| `Content-Type` | Request | `application/json` on POST |\n| `Idempotency-Key` | Request | Safe retries of `POST /v1/pack` and `POST /v1/projects/{id}/pack` |\n| `X-Request-Id` | Response | Request id (`req_…`), on every response |\n| `X-RateLimit-Limit` / `-Remaining` / `-Reset` | Response | The tightest rate window |\n| `X-Credits-Remaining` | Response | Available credits after the request |\n| `Retry-After` | Response | Seconds to wait, on 429 |\n| `Idempotent-Replayed` | Response | `true` when a key replayed an earlier job |\n\n## Machine-readable\n\n- [openapi.json](https://runner.cargoduo.com/api/public/api-docs/openapi.json): OpenAPI 3.1, every endpoint and schema.\n- [llms-full.txt](https://runner.cargoduo.com/api/public/api-docs/llms-full.txt): the integration playbook for coding agents.\n\n## Related\n\n- [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md): API keys, scopes and IP allowlists.\n- [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md): One error envelope, stable codes, clear retry rules.\n- [Rate limits \u0026 limits](https://runner.cargoduo.com/api/public/api-docs/pages/limits.md): Request rates, job size, concurrency and retention.\n"},{"slug":"create-pack-job","title":"Create a packing job","section":"API reference","group":"Pack","summary":"Validates the request, reserves the worst-case price from your credits and queues a job.","keywords":"post /v1/pack createpackjob","method":"POST","path":"/v1/pack","operation_id":"createPackJob","related":["quickstart","idempotency","credits","results"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/create-pack-job.md","markdown":"# Create a packing job\n\nValidates the request, reserves the worst-case price from your credits and queues a job.\n\n```http\nPOST https://runner.cargoduo.com/v1/pack\n```\n\nAnswers 202 with the job; poll GET /v1/jobs/{id}, or pass ?wait= to hold the response until it ends. Validation failures (422) are free and create no job.\n\n## Authentication\n\nBearer API key with the `pack:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Query parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `wait` | integer | No | Seconds to hold the response for the job to finish. Finished in time: 200 with the result inlined. Otherwise: 202. Default: `0`. 0–60. |\n\n## Header parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `Idempotency-Key` | string | No | Any unique string, such as a UUID or your order id. The same key within 24 h returns the original job instead of creating and charging a new one. Max 128 characters. |\n\n## Body parameters\n\nOne packing job: the vehicles available, the items to load, optional groups and options. Same body for POST /v1/pack and POST /v1/estimate.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `reference` | string | No | Your own reference (order number, shipment id…). Echoed back on the job. Max 128 characters. |\n| `vehicles` | Vehicle[] | Yes | Vehicle definitions. Each one may have quantity \u003e 1. At least one. |\n| `items` | Item[] | Yes | Item definitions. Each one may have quantity \u003e 1. At least one. |\n| `groups` | Group[] | No | Optional groups (customers, stops, orders). Required for every group an item names when `options.group_routing` is not `\"none\"`; otherwise groups are created from the item group names in order of appearance. |\n| `options` | Options | No | Packing options. Every field is optional. |\n\n### Vehicle\n\nA vehicle definition: a truck, trailer, container, pallet… Either name a saved vehicle with `vehicle_id` (a preset or one of yours, from GET /v1/vehicles) or give `type` + dimensions + `max_weight`. Fields sent next to `vehicle_id` override the saved values.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Your identifier, unique within the request. Results refer to it as vehicle_id. Defaults to vehicle_id. Max 128 characters. |\n| `vehicle_id` | string | No | A vehicle id from GET /v1/vehicles: a preset or one of your organization's vehicles (veh_…). Supplies type, name, dimensions, payload and every other saved value. |\n| `type` | string | No | Vehicle type. Required unless vehicle_id is given. Only the types listed in GET /v1/limits (allowed_vehicle_types) are accepted. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `name` | string | No | Display name (defaults to the saved vehicle's name or the id). Max 255 characters. |\n| `length` | number | No | Internal cargo-space length, along x (from the front wall toward the door). Required unless vehicle_id is given. Unit: `cm`. |\n| `width` | number | No | Internal cargo-space width, along z. Required unless vehicle_id is given. Unit: `cm`. |\n| `height` | number | No | Internal cargo-space height, along y. Required unless vehicle_id is given. Unit: `cm`. |\n| `max_weight` | number | No | Maximum payload. Required unless vehicle_id is given. Unit: `kg`. |\n| `quantity` | integer | No | How many identical vehicles of this definition are available. Default: `1`. 1–100. |\n| `floor_load_capacity` | number | No | Floor load capacity: 0 or more (0 = no limit). Unit: `kg/m²`. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). Default: `false`. |\n| `aux_deck_height` | number | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. At most 32 entries. |\n| `axle_positions` | number[] | No | Axle (support point) positions measured from the front of the cargo area; negative = in front of it. Same length as axle_max_loads. Each between −300 (an axle ahead of the cargo area, e.g. a rigid truck's front axle under the cab) and the vehicle length. At most 10 entries. Unit: `cm`. Requires access (`axle_loads`). |\n| `axle_max_loads` | number[] | No | Maximum cargo load per axle, in the order of axle_positions. Each greater than 0 and at most 100000. At most 10 entries. Unit: `kg`. Requires access (`axle_loads`). |\n| `cog_range_x` | number | No | Allowed centre-of-gravity deviation from the middle of the length: 0 to length / 2. Unit: `cm`. Requires access (`axle_loads`). |\n| `cog_range_y` | number | No | Allowed centre-of-gravity deviation from the middle of the width: 0 to width / 2. Unit: `cm`. Requires access (`axle_loads`). |\n| `tractor_weight` | number | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `empty_trailer_weight` | number | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `front_axle_to_middle_axle` | number | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_tractor_cog` | number | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_kingpin` | number | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. Requires access (`axle_loads`). |\n| `kingpin_to_rear_axle` | number | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_cog_to_rear_axle` | number | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_start_to_kingpin` | number | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. Requires access (`axle_loads`). |\n| `max_middle_axle_weight` | number | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. Requires access (`axle_loads`). |\n| `max_rear_axle_weight` | number | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. Requires access (`axle_loads`). |\n\n### Rect\n\nA rectangle on the vehicle floor (min-corner origin).\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n### Item\n\nAn item definition (one SKU / package type). `quantity` identical units are loaded; each unit is reported as one placement with an `instance` number. Name a saved template with `template_id` and send only what differs.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Your identifier, unique within the request. Results refer to it as item_id. Defaults to template_id. Max 128 characters. |\n| `template_id` | string | No | A template id (tpl_…) from GET /v1/templates. Supplies every item field; fields sent next to it override them. |\n| `label` | string | No | Display label (defaults to the id). Max 255 characters. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. Default: `\"box\"`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | Yes | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | Yes | Weight of ONE unit. Unit: `kg`. |\n| `quantity` | integer | No | Number of identical units. Default: `1`. 1–10000. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. Default: `true`. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Default: `0`. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. Default: `\"all\"`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. Default: `false`. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. Default: `0`. |\n| `group` | string | No | Name of the group this item belongs to (see `groups` and `options.group_routing`). |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Default: `0`. Unit: `cm`. |\n\n### Group\n\nA group of items (a customer, a stop, an order). With group routing, group order decides the loading sequence: order 1 is loaded first (deepest, at the front wall), the highest order last (at the door). Items without a group go last.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | Yes | Group name, referenced by Item.group. Max 128 characters. |\n| `order` | integer | No | Loading order (lower = loaded first). Defaults to the position in the list. |\n| `color` | string | No | Display colour as #rrggbb. |\n\n### Options\n\nPacking options. Every field is optional.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `packing_mode` | string | No | `auto` = when the listed vehicles are full, more copies of the most suitable vehicle are added (up to the job's vehicle limit); `fleet_only` = only the listed vehicles are used, anything that does not fit is reported as unplaced. One of: `auto`, `fleet_only`. Default: `\"auto\"`. |\n| `group_routing` | string | No | `none` = groups are informational; `wall` = each group gets its own section of the vehicle; `routing` = groups load in order and a later group may sit on or beside an earlier one but never below it (multi-stop delivery); `no_stack` = groups load in order side by side, never on top of each other. One of: `none`, `wall`, `routing`, `no_stack`. Default: `\"none\"`. |\n| `allow_overweight` | boolean | No | Ignore vehicle payload limits. Default: `false`. |\n| `max_stack_items` | integer | No | Maximum items in one stack. 0 = unlimited. Default: `0`. |\n| `max_stack_density_kg_m2` | number | No | Maximum stack weight per floor area. 0 = unlimited. Default: `0`. Unit: `kg/m²`. |\n| `lock_rotations` | boolean | No | Keep the first orientation that fits the vehicle height. Default: `false`. |\n| `group_identical_bins` | boolean | No | Treat identical vehicles as one pool. Default: `false`. |\n| `time_limit` | number | No | Upper bound on compute time for the whole job. 0 = automatic (capped at max_time_limit_seconds from GET /v1/limits). When the limit is reached the best plan found so far is returned. You pay only for the compute time actually used. Default: `0`. Unit: `s`. |\n| `enforce_axle` | boolean | No | Reject placements that overload an axle group, on vehicles that carry all ten axle-model fields. Default: `false`. Requires access (`axle_loads`). |\n\n## Example request\n\n```bash\ncurl -X POST \"https://runner.cargoduo.com/v1/pack\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\" \\\n  -H \"Idempotency-Key: order-1042-v1\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"reference\": \"order-1042\",\n    \"vehicles\": [\n      {\n        \"id\": \"trailer\",\n        \"type\": \"trailer\",\n        \"name\": \"13.6 m trailer\",\n        \"length\": 1360,\n        \"width\": 245,\n        \"height\": 270,\n        \"max_weight\": 24000\n      }\n    ],\n    \"items\": [\n      {\n        \"id\": \"pallet-eu\",\n        \"label\": \"Euro pallet\",\n        \"length\": 120,\n        \"width\": 80,\n        \"height\": 140,\n        \"weight\": 450,\n        \"quantity\": 18,\n        \"stackable\": false\n      },\n      {\n        \"id\": \"carton-a\",\n        \"label\": \"Carton A\",\n        \"length\": 60,\n        \"width\": 40,\n        \"height\": 40,\n        \"weight\": 12,\n        \"quantity\": 40,\n        \"rotation\": \"upright\"\n      }\n    ],\n    \"options\": {\n      \"packing_mode\": \"auto\"\n    }\n  }'\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/pack\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n    \"Idempotency-Key\": \"order-1042-v1\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"reference\": \"order-1042\",\n    \"vehicles\": [\n      {\n        \"id\": \"trailer\",\n        \"type\": \"trailer\",\n        \"name\": \"13.6 m trailer\",\n        \"length\": 1360,\n        \"width\": 245,\n        \"height\": 270,\n        \"max_weight\": 24000\n      }\n    ],\n    \"items\": [\n      {\n        \"id\": \"pallet-eu\",\n        \"label\": \"Euro pallet\",\n        \"length\": 120,\n        \"width\": 80,\n        \"height\": 140,\n        \"weight\": 450,\n        \"quantity\": 18,\n        \"stackable\": false\n      },\n      {\n        \"id\": \"carton-a\",\n        \"label\": \"Carton A\",\n        \"length\": 60,\n        \"width\": 40,\n        \"height\": 40,\n        \"weight\": 12,\n        \"quantity\": 40,\n        \"rotation\": \"upright\"\n      }\n    ],\n    \"options\": {\n      \"packing_mode\": \"auto\"\n    }\n  }),\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.post(\n    \"https://runner.cargoduo.com/v1/pack\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\", \"Idempotency-Key\": \"order-1042-v1\"},\n    json={\n        \"reference\": \"order-1042\",\n        \"vehicles\": [\n            {\n                \"id\": \"trailer\",\n                \"type\": \"trailer\",\n                \"name\": \"13.6 m trailer\",\n                \"length\": 1360,\n                \"width\": 245,\n                \"height\": 270,\n                \"max_weight\": 24000,\n            },\n        ],\n        \"items\": [\n            {\n                \"id\": \"pallet-eu\",\n                \"label\": \"Euro pallet\",\n                \"length\": 120,\n                \"width\": 80,\n                \"height\": 140,\n                \"weight\": 450,\n                \"quantity\": 18,\n                \"stackable\": False,\n            },\n            {\n                \"id\": \"carton-a\",\n                \"label\": \"Carton A\",\n                \"length\": 60,\n                \"width\": 40,\n                \"height\": 40,\n                \"weight\": 12,\n                \"quantity\": 40,\n                \"rotation\": \"upright\",\n            },\n        ],\n        \"options\": {\n            \"packing_mode\": \"auto\",\n        },\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`202` Accepted\n\n```json\n{\n  \"id\": \"job_4bK9mT2xQe7LpW1zRc8VdN3f\",\n  \"object\": \"job\",\n  \"status\": \"queued\",\n  \"reference\": \"order-1042\",\n  \"source\": \"api\",\n  \"created_at\": \"2026-10-19T09:12:03Z\",\n  \"started_at\": null,\n  \"finished_at\": null,\n  \"progress\": {\n    \"stage\": \"queued\",\n    \"vehicles_done\": 0,\n    \"placed_items\": 0\n  },\n  \"summary\": {\n    \"items_requested\": 58,\n    \"items_placed\": 0,\n    \"items_unplaced\": 0,\n    \"vehicles_used\": 0,\n    \"total_weight\": 0,\n    \"volume_utilization\": 0,\n    \"queue_ms\": 0,\n    \"compute_ms\": 0\n  },\n  \"cost\": {\n    \"credits\": 0,\n    \"reserved_credits\": 1.11,\n    \"rate_card_version\": 1,\n    \"breakdown\": {\n      \"base\": 0,\n      \"vehicles\": 0,\n      \"compute\": 0,\n      \"large_load\": 0\n    }\n  },\n  \"error\": null,\n  \"warnings\": [],\n  \"result_url\": \"https://runner.cargoduo.com/v1/jobs/job_4bK9mT2xQe7LpW1zRc8VdN3f/result\",\n  \"pdf_url\": \"\",\n  \"expires_at\": \"2026-11-02T09:12:03Z\"\n}\n```\n\n## Responses\n\n### 202 Accepted\n\nJob accepted (queued or running). Returns `Job`.\n\n### 200 OK\n\nJob finished within ?wait, result inlined. Also an idempotent replay of a finished job. Returns `Job`.\n\n### 400 Bad request\n\n| Code | Meaning |\n|---|---|\n| `invalid_json` | The body is not valid JSON or a field has the wrong type. |\n| `idempotency_key_invalid` | The Idempotency-Key header is longer than 128 characters. |\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 402 Payment required\n\n| Code | Meaning |\n|---|---|\n| `insufficient_credits` | Available credits do not cover the smallest possible run of this job. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 413 Payload too large\n\n| Code | Meaning |\n|---|---|\n| `payload_too_large` | The request body exceeds 10 MB. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `too_many_jobs` | The organization already has the maximum number of queued + running jobs. |\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Job object\n\nA packing job. Created by POST /v1/pack; poll GET /v1/jobs/{id} until `status` is `succeeded`, `failed` or `canceled`.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Job id (job_…). |\n| `object` | string | Yes | Always `job`. One of: `job`. |\n| `status` | string | Yes | `queued` → `running` → `succeeded` \\| `failed` \\| `canceled`. One of: `queued`, `running`, `succeeded`, `failed`, `canceled`. |\n| `reference` | string | No | Your reference from the request. |\n| `source` | string | No | Where the job was created. One of: `api`, `playground`. |\n| `project_id` | string | No | The project (prj_…) a POST /v1/projects/{id}/pack job saves its plan into. Absent on other jobs. |\n| `created_at` | string | Yes | RFC 3339 timestamp. |\n| `started_at` | string \\| null | No | When computing started (RFC 3339) or `null`. |\n| `finished_at` | string \\| null | No | When the job ended (RFC 3339) or `null`. |\n| `progress` | JobProgress | No | Live progress while queued/running. |\n| `summary` | JobSummary | No | Counts and timings. |\n| `cost` | JobCost | No | Credits charged (final once the job ended) and the reservation held while it ran. |\n| `error` | JobError \\| null | No | Why the job failed or was canceled; `null` otherwise. |\n| `warnings` | Warning[] | No | Non-blocking remarks about the request (also repeated in the result). |\n| `result_url` | string | No | GET this URL for the full result once `status` is `succeeded`. |\n| `pdf_url` | string | No | A browser link that renders the load plan as a PDF (3D views + loading steps). Valid until expires_at. Empty after expiry. |\n| `expires_at` | string | No | When the request/result bodies and the PDF link are deleted (RFC 3339). Job metadata and charges are kept. |\n| `result` | Result | No | The full result, inlined only when requested (?include=result) or when POST /v1/pack?wait= finished in time. |\n\n### JobProgress\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `stage` | string | No | Current stage, such as `queued`, `running`, `finalizing` or `done`. Informational: branch on `Job.status`. |\n| `vehicles_done` | integer | No | Vehicles packed so far. |\n| `placed_items` | integer | No | Item units placed so far. |\n\n### JobSummary\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `items_requested` | integer | No | Total item units (sum of quantities). |\n| `items_placed` | integer | No | Units placed. |\n| `items_unplaced` | integer | No | Units that could not be placed. |\n| `vehicles_used` | integer | No | Vehicles that carry at least one item. |\n| `total_weight` | number | No | Weight loaded. Unit: `kg`. |\n| `volume_utilization` | number | No | Average volume utilization of the used vehicles, 0–1. |\n| `queue_ms` | integer | No | Time spent waiting for a compute slot (not billed). |\n| `compute_ms` | integer | No | Compute time (billed). |\n\n### JobCost\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `credits` | number | No | Credits charged (0 until the job ends). Failed jobs are not charged, except time_limit_exceeded, which is charged like a canceled job. Unit: `credits`. |\n| `reserved_credits` | number | No | Credits held while the job was queued/running (the worst-case price). Released when it ends. Unit: `credits`. |\n| `rate_card_version` | integer | No | Rate card the job is priced with. |\n| `breakdown` | CostBreakdown | No | Itemized price. |\n\n### CostBreakdown\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `base` | number | No | Per-job fee. Unit: `credits`. |\n| `vehicles` | number | No | Per loaded vehicle (not charged for canceled or time_limit_exceeded jobs). Unit: `credits`. |\n| `compute` | number | No | Per compute-second, pro-rated per millisecond. Unit: `credits`. |\n| `large_load` | number | No | Surcharge per started 100 items above the large-load threshold. Unit: `credits`. |\n\n### JobError\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code (see Errors). |\n| `message` | string | No | English explanation. |\n\n### Warning\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code. |\n| `message` | string | No | English explanation. |\n\n### Result\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `job_id` | string | No | The job. |\n| `vehicles` | ResultVehicle[] | No | Loaded vehicles in loading order (empty vehicles are left out). |\n| `unplaced` | UnplacedItem[] | No | Units that could not be placed, with the reason. |\n| `warnings` | Warning[] | No | Remarks about the plan. |\n| `pdf_url` | string | No | Browser link that renders the plan as a PDF. |\n| `expires_at` | string | No | When this result is deleted (RFC 3339). |\n\n### ResultVehicle\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `index` | integer | No | 0-based position in the plan. |\n| `vehicle_id` | string | No | Your vehicle definition id (for a project job: the slot id, pv_…). |\n| `copy` | integer | No | 1-based copy number of that definition (quantity \u003e 1, or added in auto mode). |\n| `auto_added` | boolean | No | Added by `packing_mode` `auto` beyond the `quantity` you listed. |\n| `name` | string | No | Vehicle name. |\n| `type` | string | No | Vehicle type. |\n| `length` | number | No | Cargo-space length. Unit: `cm`. |\n| `width` | number | No | Cargo-space width. Unit: `cm`. |\n| `height` | number | No | Cargo-space height. Unit: `cm`. |\n| `max_weight` | number | No | Payload limit. Unit: `kg`. |\n| `items_placed` | integer | No | Units in this vehicle. |\n| `total_weight` | number | No | Weight in this vehicle. Unit: `kg`. |\n| `volume_utilization` | number | No | Used volume / cargo-space volume, 0–1. |\n| `weight_utilization` | number | No | total_weight / max_weight, 0–1 (may exceed 1 with allow_overweight). |\n| `center_of_gravity` | Point | No | Centre of gravity of the load (cm). |\n| `placements` | Placement[] | No | Every unit in this vehicle. |\n| `axle_loads` | AxleLoad[] | No | Load per axle (vehicles with axle data only). Requires access (`axle_loads`). |\n\n### Point\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | No | Unit: `cm`. |\n| `y` | number | No | Unit: `cm`. |\n| `z` | number | No | Unit: `cm`. |\n\n### Placement\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id (for a project job: the item id, item_…). |\n| `instance` | integer | No | 1-based unit number within that item definition (unique across the whole plan). |\n| `label` | string | No | Item label. |\n| `x` | number | No | Min-corner position along the length. Unit: `cm`. |\n| `y` | number | No | Min-corner height above the floor. Unit: `cm`. |\n| `z` | number | No | Min-corner position across the width. Unit: `cm`. |\n| `length` | number | No | Size along x as placed. Unit: `cm`. |\n| `width` | number | No | Size along z as placed. Unit: `cm`. |\n| `height` | number | No | Size along y as placed. Unit: `cm`. |\n| `rotated` | boolean | No | Placed in a different orientation than defined. |\n| `orientation` | string | No | Which defined side lies along x, z and y: `lwh` = as defined, `wlh` = turned 90° around the vertical axis, `lhw`, `hwl`, `whl`, `hlw` = tipped onto a side. |\n| `step` | integer | No | 1-based loading step within the vehicle (load in ascending order). |\n| `weight` | number | No | Unit weight. Unit: `kg`. |\n\n### AxleLoad\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | No | `Axle 1`, `Axle 2`… in the order of `axle_positions`. |\n| `position` | number | No | Axle position from the front of the cargo area. Unit: `cm`. |\n| `load` | number | No | Cargo load on the axle. Unit: `kg`. |\n| `max` | number | No | Configured maximum (0 = none). Unit: `kg`. |\n| `ok` | boolean | No | `load` ≤ `max` (always `true` without a maximum). |\n\n### UnplacedItem\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id. |\n| `instance` | integer | No | 1-based unit number. |\n| `label` | string | No | Item label. |\n| `reason` | string | No | Why it could not be placed. |\n\n\n## Related\n\n- [Quickstart](https://runner.cargoduo.com/api/public/api-docs/pages/quickstart.md): Create a key, send one request, read the plan.\n- [Idempotency](https://runner.cargoduo.com/api/public/api-docs/pages/idempotency.md): Retry POST /v1/pack without creating a second job.\n- [Credits \u0026 pricing](https://runner.cargoduo.com/api/public/api-docs/pages/credits.md): What a job costs and where credits come from.\n- [Results \u0026 coordinates](https://runner.cargoduo.com/api/public/api-docs/pages/results.md): Where every item sits, in which orientation and loading order.\n"},{"slug":"estimate-pack-job","title":"Estimate a packing job","section":"API reference","group":"Pack","summary":"Runs the same validation as POST /v1/pack and returns the price range, without creating a job or reserving credits.","keywords":"post /v1/estimate estimatepackjob","method":"POST","path":"/v1/estimate","operation_id":"estimatePackJob","related":["create-pack-job","credits","limits"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/estimate-pack-job.md","markdown":"# Estimate a packing job\n\nRuns the same validation as POST /v1/pack and returns the price range, without creating a job or reserving credits.\n\n```http\nPOST https://runner.cargoduo.com/v1/estimate\n```\n\nFree. An invalid request answers 200 with `valid: false` and every problem in `errors[]`.\n\n## Authentication\n\nBearer API key with the `pack:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Body parameters\n\nOne packing job: the vehicles available, the items to load, optional groups and options. Same body for POST /v1/pack and POST /v1/estimate.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `reference` | string | No | Your own reference (order number, shipment id…). Echoed back on the job. Max 128 characters. |\n| `vehicles` | Vehicle[] | Yes | Vehicle definitions. Each one may have quantity \u003e 1. At least one. |\n| `items` | Item[] | Yes | Item definitions. Each one may have quantity \u003e 1. At least one. |\n| `groups` | Group[] | No | Optional groups (customers, stops, orders). Required for every group an item names when `options.group_routing` is not `\"none\"`; otherwise groups are created from the item group names in order of appearance. |\n| `options` | Options | No | Packing options. Every field is optional. |\n\n### Vehicle\n\nA vehicle definition: a truck, trailer, container, pallet… Either name a saved vehicle with `vehicle_id` (a preset or one of yours, from GET /v1/vehicles) or give `type` + dimensions + `max_weight`. Fields sent next to `vehicle_id` override the saved values.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Your identifier, unique within the request. Results refer to it as vehicle_id. Defaults to vehicle_id. Max 128 characters. |\n| `vehicle_id` | string | No | A vehicle id from GET /v1/vehicles: a preset or one of your organization's vehicles (veh_…). Supplies type, name, dimensions, payload and every other saved value. |\n| `type` | string | No | Vehicle type. Required unless vehicle_id is given. Only the types listed in GET /v1/limits (allowed_vehicle_types) are accepted. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `name` | string | No | Display name (defaults to the saved vehicle's name or the id). Max 255 characters. |\n| `length` | number | No | Internal cargo-space length, along x (from the front wall toward the door). Required unless vehicle_id is given. Unit: `cm`. |\n| `width` | number | No | Internal cargo-space width, along z. Required unless vehicle_id is given. Unit: `cm`. |\n| `height` | number | No | Internal cargo-space height, along y. Required unless vehicle_id is given. Unit: `cm`. |\n| `max_weight` | number | No | Maximum payload. Required unless vehicle_id is given. Unit: `kg`. |\n| `quantity` | integer | No | How many identical vehicles of this definition are available. Default: `1`. 1–100. |\n| `floor_load_capacity` | number | No | Floor load capacity: 0 or more (0 = no limit). Unit: `kg/m²`. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). Default: `false`. |\n| `aux_deck_height` | number | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. At most 32 entries. |\n| `axle_positions` | number[] | No | Axle (support point) positions measured from the front of the cargo area; negative = in front of it. Same length as axle_max_loads. Each between −300 (an axle ahead of the cargo area, e.g. a rigid truck's front axle under the cab) and the vehicle length. At most 10 entries. Unit: `cm`. Requires access (`axle_loads`). |\n| `axle_max_loads` | number[] | No | Maximum cargo load per axle, in the order of axle_positions. Each greater than 0 and at most 100000. At most 10 entries. Unit: `kg`. Requires access (`axle_loads`). |\n| `cog_range_x` | number | No | Allowed centre-of-gravity deviation from the middle of the length: 0 to length / 2. Unit: `cm`. Requires access (`axle_loads`). |\n| `cog_range_y` | number | No | Allowed centre-of-gravity deviation from the middle of the width: 0 to width / 2. Unit: `cm`. Requires access (`axle_loads`). |\n| `tractor_weight` | number | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `empty_trailer_weight` | number | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `front_axle_to_middle_axle` | number | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_tractor_cog` | number | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_kingpin` | number | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. Requires access (`axle_loads`). |\n| `kingpin_to_rear_axle` | number | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_cog_to_rear_axle` | number | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_start_to_kingpin` | number | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. Requires access (`axle_loads`). |\n| `max_middle_axle_weight` | number | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. Requires access (`axle_loads`). |\n| `max_rear_axle_weight` | number | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. Requires access (`axle_loads`). |\n\n### Rect\n\nA rectangle on the vehicle floor (min-corner origin).\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n### Item\n\nAn item definition (one SKU / package type). `quantity` identical units are loaded; each unit is reported as one placement with an `instance` number. Name a saved template with `template_id` and send only what differs.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Your identifier, unique within the request. Results refer to it as item_id. Defaults to template_id. Max 128 characters. |\n| `template_id` | string | No | A template id (tpl_…) from GET /v1/templates. Supplies every item field; fields sent next to it override them. |\n| `label` | string | No | Display label (defaults to the id). Max 255 characters. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. Default: `\"box\"`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | Yes | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | Yes | Weight of ONE unit. Unit: `kg`. |\n| `quantity` | integer | No | Number of identical units. Default: `1`. 1–10000. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. Default: `true`. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Default: `0`. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. Default: `\"all\"`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. Default: `false`. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. Default: `0`. |\n| `group` | string | No | Name of the group this item belongs to (see `groups` and `options.group_routing`). |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Default: `0`. Unit: `cm`. |\n\n### Group\n\nA group of items (a customer, a stop, an order). With group routing, group order decides the loading sequence: order 1 is loaded first (deepest, at the front wall), the highest order last (at the door). Items without a group go last.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | Yes | Group name, referenced by Item.group. Max 128 characters. |\n| `order` | integer | No | Loading order (lower = loaded first). Defaults to the position in the list. |\n| `color` | string | No | Display colour as #rrggbb. |\n\n### Options\n\nPacking options. Every field is optional.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `packing_mode` | string | No | `auto` = when the listed vehicles are full, more copies of the most suitable vehicle are added (up to the job's vehicle limit); `fleet_only` = only the listed vehicles are used, anything that does not fit is reported as unplaced. One of: `auto`, `fleet_only`. Default: `\"auto\"`. |\n| `group_routing` | string | No | `none` = groups are informational; `wall` = each group gets its own section of the vehicle; `routing` = groups load in order and a later group may sit on or beside an earlier one but never below it (multi-stop delivery); `no_stack` = groups load in order side by side, never on top of each other. One of: `none`, `wall`, `routing`, `no_stack`. Default: `\"none\"`. |\n| `allow_overweight` | boolean | No | Ignore vehicle payload limits. Default: `false`. |\n| `max_stack_items` | integer | No | Maximum items in one stack. 0 = unlimited. Default: `0`. |\n| `max_stack_density_kg_m2` | number | No | Maximum stack weight per floor area. 0 = unlimited. Default: `0`. Unit: `kg/m²`. |\n| `lock_rotations` | boolean | No | Keep the first orientation that fits the vehicle height. Default: `false`. |\n| `group_identical_bins` | boolean | No | Treat identical vehicles as one pool. Default: `false`. |\n| `time_limit` | number | No | Upper bound on compute time for the whole job. 0 = automatic (capped at max_time_limit_seconds from GET /v1/limits). When the limit is reached the best plan found so far is returned. You pay only for the compute time actually used. Default: `0`. Unit: `s`. |\n| `enforce_axle` | boolean | No | Reject placements that overload an axle group, on vehicles that carry all ten axle-model fields. Default: `false`. Requires access (`axle_loads`). |\n\n## Example request\n\n```bash\ncurl -X POST \"https://runner.cargoduo.com/v1/estimate\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"reference\": \"order-1042\",\n    \"vehicles\": [\n      {\n        \"id\": \"trailer\",\n        \"type\": \"trailer\",\n        \"name\": \"13.6 m trailer\",\n        \"length\": 1360,\n        \"width\": 245,\n        \"height\": 270,\n        \"max_weight\": 24000\n      }\n    ],\n    \"items\": [\n      {\n        \"id\": \"pallet-eu\",\n        \"label\": \"Euro pallet\",\n        \"length\": 120,\n        \"width\": 80,\n        \"height\": 140,\n        \"weight\": 450,\n        \"quantity\": 18,\n        \"stackable\": false\n      },\n      {\n        \"id\": \"carton-a\",\n        \"label\": \"Carton A\",\n        \"length\": 60,\n        \"width\": 40,\n        \"height\": 40,\n        \"weight\": 12,\n        \"quantity\": 40,\n        \"rotation\": \"upright\"\n      }\n    ],\n    \"options\": {\n      \"packing_mode\": \"auto\"\n    }\n  }'\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/estimate\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"reference\": \"order-1042\",\n    \"vehicles\": [\n      {\n        \"id\": \"trailer\",\n        \"type\": \"trailer\",\n        \"name\": \"13.6 m trailer\",\n        \"length\": 1360,\n        \"width\": 245,\n        \"height\": 270,\n        \"max_weight\": 24000\n      }\n    ],\n    \"items\": [\n      {\n        \"id\": \"pallet-eu\",\n        \"label\": \"Euro pallet\",\n        \"length\": 120,\n        \"width\": 80,\n        \"height\": 140,\n        \"weight\": 450,\n        \"quantity\": 18,\n        \"stackable\": false\n      },\n      {\n        \"id\": \"carton-a\",\n        \"label\": \"Carton A\",\n        \"length\": 60,\n        \"width\": 40,\n        \"height\": 40,\n        \"weight\": 12,\n        \"quantity\": 40,\n        \"rotation\": \"upright\"\n      }\n    ],\n    \"options\": {\n      \"packing_mode\": \"auto\"\n    }\n  }),\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.post(\n    \"https://runner.cargoduo.com/v1/estimate\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    json={\n        \"reference\": \"order-1042\",\n        \"vehicles\": [\n            {\n                \"id\": \"trailer\",\n                \"type\": \"trailer\",\n                \"name\": \"13.6 m trailer\",\n                \"length\": 1360,\n                \"width\": 245,\n                \"height\": 270,\n                \"max_weight\": 24000,\n            },\n        ],\n        \"items\": [\n            {\n                \"id\": \"pallet-eu\",\n                \"label\": \"Euro pallet\",\n                \"length\": 120,\n                \"width\": 80,\n                \"height\": 140,\n                \"weight\": 450,\n                \"quantity\": 18,\n                \"stackable\": False,\n            },\n            {\n                \"id\": \"carton-a\",\n                \"label\": \"Carton A\",\n                \"length\": 60,\n                \"width\": 40,\n                \"height\": 40,\n                \"weight\": 12,\n                \"quantity\": 40,\n                \"rotation\": \"upright\",\n            },\n        ],\n        \"options\": {\n            \"packing_mode\": \"auto\",\n        },\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"valid\": true,\n  \"errors\": [],\n  \"warnings\": [],\n  \"items\": 58,\n  \"max_vehicles\": 3,\n  \"estimated_cost\": {\n    \"min_credits\": 0.26,\n    \"max_credits\": 1.11\n  },\n  \"estimated_seconds\": 90,\n  \"balance\": {\n    \"available_credits\": 147.75\n  }\n}\n```\n\n## Responses\n\n### 200 OK\n\nValidation result and price range. Returns `Estimate`.\n\n### 400 Bad request\n\n| Code | Meaning |\n|---|---|\n| `invalid_json` | The body is not valid JSON or a field has the wrong type. |\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 413 Payload too large\n\n| Code | Meaning |\n|---|---|\n| `payload_too_large` | The request body exceeds 10 MB. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Estimate object\n\nFree validation + price estimate of a `PackRequest`.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `valid` | boolean | No | The request would be accepted by POST /v1/pack. |\n| `errors` | ErrorDetail[] | No | Validation errors (empty when valid). |\n| `warnings` | Warning[] | No | Non-blocking remarks. |\n| `items` | integer | No | Total item units. |\n| `max_vehicles` | integer | No | Most vehicles the job may load. |\n| `estimated_cost` | CostRange | No | Price range in credits. |\n| `estimated_seconds` | number | No | Compute budget the job would get (an upper bound; most jobs finish sooner). Unit: `s`. |\n| `balance` | EstimateBalance | No | Your available credits. |\n\n### ErrorDetail\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `path` | string | No | JSON path of the offending field, e.g. items[3].length. |\n| `code` | string | No | `required`, `invalid_value`, `out_of_range`, `duplicate_id`, `unknown_vehicle`, `unknown_template`, `type_not_allowed`, `unknown_group`, `does_not_fit`, `too_many_items`, `too_many_vehicles`, `unknown_field`, `invalid_json`. |\n| `message` | string | No | English explanation. |\n\n### Warning\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code. |\n| `message` | string | No | English explanation. |\n\n### CostRange\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `min_credits` | number | No | Lowest plausible price (fewest vehicles, no compute). Unit: `credits`. |\n| `max_credits` | number | No | Worst case: max_vehicles loaded and the full compute budget used. This is what POST /v1/pack reserves. Unit: `credits`. |\n\n### EstimateBalance\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `available_credits` | number | No | Unit: `credits`. |\n\n\n## Related\n\n- [Create a packing job](https://runner.cargoduo.com/api/public/api-docs/pages/create-pack-job.md): Validates the request, reserves the worst-case price from your credits and queues a job.\n- [Credits \u0026 pricing](https://runner.cargoduo.com/api/public/api-docs/pages/credits.md): What a job costs and where credits come from.\n- [Rate limits \u0026 limits](https://runner.cargoduo.com/api/public/api-docs/pages/limits.md): Request rates, job size, concurrency and retention.\n"},{"slug":"list-jobs","title":"List jobs","section":"API reference","group":"Jobs","summary":"Your organization's jobs, newest first, one page at a time.","keywords":"get /v1/jobs listjobs","method":"GET","path":"/v1/jobs","operation_id":"listJobs","related":["get-job","results"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/list-jobs.md","markdown":"# List jobs\n\nYour organization's jobs, newest first, one page at a time.\n\n```http\nGET https://runner.cargoduo.com/v1/jobs\n```\n\n## Authentication\n\nBearer API key with the `jobs:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Query parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `limit` | integer | No | Page size. Default: `20`. 1–100. |\n| `cursor` | string | No | next_cursor of the previous page. |\n| `status` | string | No | Only jobs in this status. One of: `queued`, `running`, `succeeded`, `failed`, `canceled`. |\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/jobs?limit=2\u0026status=succeeded\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/jobs?limit=2\u0026status=succeeded\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/jobs\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    params={\n        \"limit\": 2,\n        \"status\": \"succeeded\",\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"job_4bK9mT2xQe7LpW1zRc8VdN3f\",\n      \"object\": \"job\",\n      \"status\": \"succeeded\",\n      \"reference\": \"order-1042\",\n      \"source\": \"api\",\n      \"created_at\": \"2026-10-19T09:12:03Z\",\n      \"started_at\": \"2026-10-19T09:12:03Z\",\n      \"finished_at\": \"2026-10-19T09:12:07Z\",\n      \"progress\": {\n        \"stage\": \"done\",\n        \"vehicles_done\": 1,\n        \"placed_items\": 58\n      },\n      \"summary\": {\n        \"items_requested\": 58,\n        \"items_placed\": 58,\n        \"items_unplaced\": 0,\n        \"vehicles_used\": 1,\n        \"total_weight\": 8580,\n        \"volume_utilization\": 0.3116,\n        \"queue_ms\": 12,\n        \"compute_ms\": 4210\n      },\n      \"cost\": {\n        \"credits\": 0.28105,\n        \"reserved_credits\": 1.11,\n        \"rate_card_version\": 1,\n        \"breakdown\": {\n          \"base\": 0.06,\n          \"vehicles\": 0.2,\n          \"compute\": 0.02105,\n          \"large_load\": 0\n        }\n      },\n      \"error\": null,\n      \"warnings\": [],\n      \"result_url\": \"https://runner.cargoduo.com/v1/jobs/job_4bK9mT2xQe7LpW1zRc8VdN3f/result\",\n      \"pdf_url\": \"https://app.cargoduo.com/r/Xq7Lm2Vt9KbR4cN8wZp1Hs6Yd3Fg5Jk0Ta2Ue7Wn4Ci\",\n      \"expires_at\": \"2026-11-02T09:12:03Z\"\n    },\n    {\n      \"id\": \"job_7hN2wQ5rTb9xKc3LmP8sVa1d\",\n      \"object\": \"job\",\n      \"status\": \"succeeded\",\n      \"reference\": \"order-1039\",\n      \"source\": \"api\",\n      \"created_at\": \"2026-10-18T07:12:03Z\",\n      \"started_at\": \"2026-10-18T07:12:03Z\",\n      \"finished_at\": \"2026-10-18T07:12:05Z\",\n      \"progress\": {\n        \"stage\": \"done\",\n        \"vehicles_done\": 1,\n        \"placed_items\": 24\n      },\n      \"summary\": {\n        \"items_requested\": 24,\n        \"items_placed\": 24,\n        \"items_unplaced\": 0,\n        \"vehicles_used\": 1,\n        \"total_weight\": 6240,\n        \"volume_utilization\": 0.2213,\n        \"queue_ms\": 9,\n        \"compute_ms\": 2870\n      },\n      \"cost\": {\n        \"credits\": 0.27435,\n        \"reserved_credits\": 1.11,\n        \"rate_card_version\": 1,\n        \"breakdown\": {\n          \"base\": 0.06,\n          \"vehicles\": 0.2,\n          \"compute\": 0.01435,\n          \"large_load\": 0\n        }\n      },\n      \"error\": null,\n      \"warnings\": [],\n      \"result_url\": \"https://runner.cargoduo.com/v1/jobs/job_7hN2wQ5rTb9xKc3LmP8sVa1d/result\",\n      \"pdf_url\": \"https://app.cargoduo.com/r/Xq7Lm2Vt9KbR4cN8wZp1Hs6Yd3Fg5Jk0Ta2Ue7Wn4Ci\",\n      \"expires_at\": \"2026-11-01T07:12:03Z\"\n    }\n  ],\n  \"next_cursor\": \"job_7hN2wQ5rTb9xKc3LmP8sVa1d\"\n}\n```\n\n## Responses\n\n### 200 OK\n\nOne page of jobs. Returns `JobList`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The JobList object\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `data` | Job[] | No |  |\n| `next_cursor` | string | No | Pass as ?cursor= for the next page; empty on the last page. |\n\n### Job\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Job id (job_…). |\n| `object` | string | Yes | Always `job`. One of: `job`. |\n| `status` | string | Yes | `queued` → `running` → `succeeded` \\| `failed` \\| `canceled`. One of: `queued`, `running`, `succeeded`, `failed`, `canceled`. |\n| `reference` | string | No | Your reference from the request. |\n| `source` | string | No | Where the job was created. One of: `api`, `playground`. |\n| `project_id` | string | No | The project (prj_…) a POST /v1/projects/{id}/pack job saves its plan into. Absent on other jobs. |\n| `created_at` | string | Yes | RFC 3339 timestamp. |\n| `started_at` | string \\| null | No | When computing started (RFC 3339) or `null`. |\n| `finished_at` | string \\| null | No | When the job ended (RFC 3339) or `null`. |\n| `progress` | JobProgress | No | Live progress while queued/running. |\n| `summary` | JobSummary | No | Counts and timings. |\n| `cost` | JobCost | No | Credits charged (final once the job ended) and the reservation held while it ran. |\n| `error` | JobError \\| null | No | Why the job failed or was canceled; `null` otherwise. |\n| `warnings` | Warning[] | No | Non-blocking remarks about the request (also repeated in the result). |\n| `result_url` | string | No | GET this URL for the full result once `status` is `succeeded`. |\n| `pdf_url` | string | No | A browser link that renders the load plan as a PDF (3D views + loading steps). Valid until expires_at. Empty after expiry. |\n| `expires_at` | string | No | When the request/result bodies and the PDF link are deleted (RFC 3339). Job metadata and charges are kept. |\n| `result` | Result | No | The full result, inlined only when requested (?include=result) or when POST /v1/pack?wait= finished in time. |\n\n### JobProgress\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `stage` | string | No | Current stage, such as `queued`, `running`, `finalizing` or `done`. Informational: branch on `Job.status`. |\n| `vehicles_done` | integer | No | Vehicles packed so far. |\n| `placed_items` | integer | No | Item units placed so far. |\n\n### JobSummary\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `items_requested` | integer | No | Total item units (sum of quantities). |\n| `items_placed` | integer | No | Units placed. |\n| `items_unplaced` | integer | No | Units that could not be placed. |\n| `vehicles_used` | integer | No | Vehicles that carry at least one item. |\n| `total_weight` | number | No | Weight loaded. Unit: `kg`. |\n| `volume_utilization` | number | No | Average volume utilization of the used vehicles, 0–1. |\n| `queue_ms` | integer | No | Time spent waiting for a compute slot (not billed). |\n| `compute_ms` | integer | No | Compute time (billed). |\n\n### JobCost\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `credits` | number | No | Credits charged (0 until the job ends). Failed jobs are not charged, except time_limit_exceeded, which is charged like a canceled job. Unit: `credits`. |\n| `reserved_credits` | number | No | Credits held while the job was queued/running (the worst-case price). Released when it ends. Unit: `credits`. |\n| `rate_card_version` | integer | No | Rate card the job is priced with. |\n| `breakdown` | CostBreakdown | No | Itemized price. |\n\n### CostBreakdown\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `base` | number | No | Per-job fee. Unit: `credits`. |\n| `vehicles` | number | No | Per loaded vehicle (not charged for canceled or time_limit_exceeded jobs). Unit: `credits`. |\n| `compute` | number | No | Per compute-second, pro-rated per millisecond. Unit: `credits`. |\n| `large_load` | number | No | Surcharge per started 100 items above the large-load threshold. Unit: `credits`. |\n\n### JobError\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code (see Errors). |\n| `message` | string | No | English explanation. |\n\n### Warning\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code. |\n| `message` | string | No | English explanation. |\n\n### Result\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `job_id` | string | No | The job. |\n| `vehicles` | ResultVehicle[] | No | Loaded vehicles in loading order (empty vehicles are left out). |\n| `unplaced` | UnplacedItem[] | No | Units that could not be placed, with the reason. |\n| `warnings` | Warning[] | No | Remarks about the plan. |\n| `pdf_url` | string | No | Browser link that renders the plan as a PDF. |\n| `expires_at` | string | No | When this result is deleted (RFC 3339). |\n\n### ResultVehicle\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `index` | integer | No | 0-based position in the plan. |\n| `vehicle_id` | string | No | Your vehicle definition id (for a project job: the slot id, pv_…). |\n| `copy` | integer | No | 1-based copy number of that definition (quantity \u003e 1, or added in auto mode). |\n| `auto_added` | boolean | No | Added by `packing_mode` `auto` beyond the `quantity` you listed. |\n| `name` | string | No | Vehicle name. |\n| `type` | string | No | Vehicle type. |\n| `length` | number | No | Cargo-space length. Unit: `cm`. |\n| `width` | number | No | Cargo-space width. Unit: `cm`. |\n| `height` | number | No | Cargo-space height. Unit: `cm`. |\n| `max_weight` | number | No | Payload limit. Unit: `kg`. |\n| `items_placed` | integer | No | Units in this vehicle. |\n| `total_weight` | number | No | Weight in this vehicle. Unit: `kg`. |\n| `volume_utilization` | number | No | Used volume / cargo-space volume, 0–1. |\n| `weight_utilization` | number | No | total_weight / max_weight, 0–1 (may exceed 1 with allow_overweight). |\n| `center_of_gravity` | Point | No | Centre of gravity of the load (cm). |\n| `placements` | Placement[] | No | Every unit in this vehicle. |\n| `axle_loads` | AxleLoad[] | No | Load per axle (vehicles with axle data only). Requires access (`axle_loads`). |\n\n### Point\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | No | Unit: `cm`. |\n| `y` | number | No | Unit: `cm`. |\n| `z` | number | No | Unit: `cm`. |\n\n### Placement\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id (for a project job: the item id, item_…). |\n| `instance` | integer | No | 1-based unit number within that item definition (unique across the whole plan). |\n| `label` | string | No | Item label. |\n| `x` | number | No | Min-corner position along the length. Unit: `cm`. |\n| `y` | number | No | Min-corner height above the floor. Unit: `cm`. |\n| `z` | number | No | Min-corner position across the width. Unit: `cm`. |\n| `length` | number | No | Size along x as placed. Unit: `cm`. |\n| `width` | number | No | Size along z as placed. Unit: `cm`. |\n| `height` | number | No | Size along y as placed. Unit: `cm`. |\n| `rotated` | boolean | No | Placed in a different orientation than defined. |\n| `orientation` | string | No | Which defined side lies along x, z and y: `lwh` = as defined, `wlh` = turned 90° around the vertical axis, `lhw`, `hwl`, `whl`, `hlw` = tipped onto a side. |\n| `step` | integer | No | 1-based loading step within the vehicle (load in ascending order). |\n| `weight` | number | No | Unit weight. Unit: `kg`. |\n\n### AxleLoad\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | No | `Axle 1`, `Axle 2`… in the order of `axle_positions`. |\n| `position` | number | No | Axle position from the front of the cargo area. Unit: `cm`. |\n| `load` | number | No | Cargo load on the axle. Unit: `kg`. |\n| `max` | number | No | Configured maximum (0 = none). Unit: `kg`. |\n| `ok` | boolean | No | `load` ≤ `max` (always `true` without a maximum). |\n\n### UnplacedItem\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id. |\n| `instance` | integer | No | 1-based unit number. |\n| `label` | string | No | Item label. |\n| `reason` | string | No | Why it could not be placed. |\n\n\n## Related\n\n- [Get a job](https://runner.cargoduo.com/api/public/api-docs/pages/get-job.md): A job's status, progress, summary and cost.\n- [Results \u0026 coordinates](https://runner.cargoduo.com/api/public/api-docs/pages/results.md): Where every item sits, in which orientation and loading order.\n"},{"slug":"get-job","title":"Get a job","section":"API reference","group":"Jobs","summary":"A job's status, progress, summary and cost.","keywords":"get /v1/jobs/{id} getjob","method":"GET","path":"/v1/jobs/{id}","operation_id":"getJob","related":["get-job-result","cancel-job","errors"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/get-job.md","markdown":"# Get a job\n\nA job's status, progress, summary and cost.\n\n```http\nGET https://runner.cargoduo.com/v1/jobs/{id}\n```\n\nPoll it every 1–2 s until `status` is `succeeded`, `failed` or `canceled`.\n\n## Authentication\n\nBearer API key with the `jobs:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Job id (job_…). |\n\n## Query parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `include` | string | No | `result` inlines the load plan once the job succeeded. One of: `result`. |\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/jobs/job_4bK9mT2xQe7LpW1zRc8VdN3f\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/jobs/job_4bK9mT2xQe7LpW1zRc8VdN3f\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/jobs/job_4bK9mT2xQe7LpW1zRc8VdN3f\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"id\": \"job_4bK9mT2xQe7LpW1zRc8VdN3f\",\n  \"object\": \"job\",\n  \"status\": \"succeeded\",\n  \"reference\": \"order-1042\",\n  \"source\": \"api\",\n  \"created_at\": \"2026-10-19T09:12:03Z\",\n  \"started_at\": \"2026-10-19T09:12:03Z\",\n  \"finished_at\": \"2026-10-19T09:12:07Z\",\n  \"progress\": {\n    \"stage\": \"done\",\n    \"vehicles_done\": 1,\n    \"placed_items\": 58\n  },\n  \"summary\": {\n    \"items_requested\": 58,\n    \"items_placed\": 58,\n    \"items_unplaced\": 0,\n    \"vehicles_used\": 1,\n    \"total_weight\": 8580,\n    \"volume_utilization\": 0.3116,\n    \"queue_ms\": 12,\n    \"compute_ms\": 4210\n  },\n  \"cost\": {\n    \"credits\": 0.28105,\n    \"reserved_credits\": 1.11,\n    \"rate_card_version\": 1,\n    \"breakdown\": {\n      \"base\": 0.06,\n      \"vehicles\": 0.2,\n      \"compute\": 0.02105,\n      \"large_load\": 0\n    }\n  },\n  \"error\": null,\n  \"warnings\": [],\n  \"result_url\": \"https://runner.cargoduo.com/v1/jobs/job_4bK9mT2xQe7LpW1zRc8VdN3f/result\",\n  \"pdf_url\": \"https://app.cargoduo.com/r/Xq7Lm2Vt9KbR4cN8wZp1Hs6Yd3Fg5Jk0Ta2Ue7Wn4Ci\",\n  \"expires_at\": \"2026-11-02T09:12:03Z\"\n}\n```\n\n## Responses\n\n### 200 OK\n\nThe job. Returns `Job`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `job_not_found` | No job with this id in your organization. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Job object\n\nA packing job. Created by POST /v1/pack; poll GET /v1/jobs/{id} until `status` is `succeeded`, `failed` or `canceled`.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Job id (job_…). |\n| `object` | string | Yes | Always `job`. One of: `job`. |\n| `status` | string | Yes | `queued` → `running` → `succeeded` \\| `failed` \\| `canceled`. One of: `queued`, `running`, `succeeded`, `failed`, `canceled`. |\n| `reference` | string | No | Your reference from the request. |\n| `source` | string | No | Where the job was created. One of: `api`, `playground`. |\n| `project_id` | string | No | The project (prj_…) a POST /v1/projects/{id}/pack job saves its plan into. Absent on other jobs. |\n| `created_at` | string | Yes | RFC 3339 timestamp. |\n| `started_at` | string \\| null | No | When computing started (RFC 3339) or `null`. |\n| `finished_at` | string \\| null | No | When the job ended (RFC 3339) or `null`. |\n| `progress` | JobProgress | No | Live progress while queued/running. |\n| `summary` | JobSummary | No | Counts and timings. |\n| `cost` | JobCost | No | Credits charged (final once the job ended) and the reservation held while it ran. |\n| `error` | JobError \\| null | No | Why the job failed or was canceled; `null` otherwise. |\n| `warnings` | Warning[] | No | Non-blocking remarks about the request (also repeated in the result). |\n| `result_url` | string | No | GET this URL for the full result once `status` is `succeeded`. |\n| `pdf_url` | string | No | A browser link that renders the load plan as a PDF (3D views + loading steps). Valid until expires_at. Empty after expiry. |\n| `expires_at` | string | No | When the request/result bodies and the PDF link are deleted (RFC 3339). Job metadata and charges are kept. |\n| `result` | Result | No | The full result, inlined only when requested (?include=result) or when POST /v1/pack?wait= finished in time. |\n\n### JobProgress\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `stage` | string | No | Current stage, such as `queued`, `running`, `finalizing` or `done`. Informational: branch on `Job.status`. |\n| `vehicles_done` | integer | No | Vehicles packed so far. |\n| `placed_items` | integer | No | Item units placed so far. |\n\n### JobSummary\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `items_requested` | integer | No | Total item units (sum of quantities). |\n| `items_placed` | integer | No | Units placed. |\n| `items_unplaced` | integer | No | Units that could not be placed. |\n| `vehicles_used` | integer | No | Vehicles that carry at least one item. |\n| `total_weight` | number | No | Weight loaded. Unit: `kg`. |\n| `volume_utilization` | number | No | Average volume utilization of the used vehicles, 0–1. |\n| `queue_ms` | integer | No | Time spent waiting for a compute slot (not billed). |\n| `compute_ms` | integer | No | Compute time (billed). |\n\n### JobCost\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `credits` | number | No | Credits charged (0 until the job ends). Failed jobs are not charged, except time_limit_exceeded, which is charged like a canceled job. Unit: `credits`. |\n| `reserved_credits` | number | No | Credits held while the job was queued/running (the worst-case price). Released when it ends. Unit: `credits`. |\n| `rate_card_version` | integer | No | Rate card the job is priced with. |\n| `breakdown` | CostBreakdown | No | Itemized price. |\n\n### CostBreakdown\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `base` | number | No | Per-job fee. Unit: `credits`. |\n| `vehicles` | number | No | Per loaded vehicle (not charged for canceled or time_limit_exceeded jobs). Unit: `credits`. |\n| `compute` | number | No | Per compute-second, pro-rated per millisecond. Unit: `credits`. |\n| `large_load` | number | No | Surcharge per started 100 items above the large-load threshold. Unit: `credits`. |\n\n### JobError\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code (see Errors). |\n| `message` | string | No | English explanation. |\n\n### Warning\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code. |\n| `message` | string | No | English explanation. |\n\n### Result\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `job_id` | string | No | The job. |\n| `vehicles` | ResultVehicle[] | No | Loaded vehicles in loading order (empty vehicles are left out). |\n| `unplaced` | UnplacedItem[] | No | Units that could not be placed, with the reason. |\n| `warnings` | Warning[] | No | Remarks about the plan. |\n| `pdf_url` | string | No | Browser link that renders the plan as a PDF. |\n| `expires_at` | string | No | When this result is deleted (RFC 3339). |\n\n### ResultVehicle\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `index` | integer | No | 0-based position in the plan. |\n| `vehicle_id` | string | No | Your vehicle definition id (for a project job: the slot id, pv_…). |\n| `copy` | integer | No | 1-based copy number of that definition (quantity \u003e 1, or added in auto mode). |\n| `auto_added` | boolean | No | Added by `packing_mode` `auto` beyond the `quantity` you listed. |\n| `name` | string | No | Vehicle name. |\n| `type` | string | No | Vehicle type. |\n| `length` | number | No | Cargo-space length. Unit: `cm`. |\n| `width` | number | No | Cargo-space width. Unit: `cm`. |\n| `height` | number | No | Cargo-space height. Unit: `cm`. |\n| `max_weight` | number | No | Payload limit. Unit: `kg`. |\n| `items_placed` | integer | No | Units in this vehicle. |\n| `total_weight` | number | No | Weight in this vehicle. Unit: `kg`. |\n| `volume_utilization` | number | No | Used volume / cargo-space volume, 0–1. |\n| `weight_utilization` | number | No | total_weight / max_weight, 0–1 (may exceed 1 with allow_overweight). |\n| `center_of_gravity` | Point | No | Centre of gravity of the load (cm). |\n| `placements` | Placement[] | No | Every unit in this vehicle. |\n| `axle_loads` | AxleLoad[] | No | Load per axle (vehicles with axle data only). Requires access (`axle_loads`). |\n\n### Point\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | No | Unit: `cm`. |\n| `y` | number | No | Unit: `cm`. |\n| `z` | number | No | Unit: `cm`. |\n\n### Placement\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id (for a project job: the item id, item_…). |\n| `instance` | integer | No | 1-based unit number within that item definition (unique across the whole plan). |\n| `label` | string | No | Item label. |\n| `x` | number | No | Min-corner position along the length. Unit: `cm`. |\n| `y` | number | No | Min-corner height above the floor. Unit: `cm`. |\n| `z` | number | No | Min-corner position across the width. Unit: `cm`. |\n| `length` | number | No | Size along x as placed. Unit: `cm`. |\n| `width` | number | No | Size along z as placed. Unit: `cm`. |\n| `height` | number | No | Size along y as placed. Unit: `cm`. |\n| `rotated` | boolean | No | Placed in a different orientation than defined. |\n| `orientation` | string | No | Which defined side lies along x, z and y: `lwh` = as defined, `wlh` = turned 90° around the vertical axis, `lhw`, `hwl`, `whl`, `hlw` = tipped onto a side. |\n| `step` | integer | No | 1-based loading step within the vehicle (load in ascending order). |\n| `weight` | number | No | Unit weight. Unit: `kg`. |\n\n### AxleLoad\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | No | `Axle 1`, `Axle 2`… in the order of `axle_positions`. |\n| `position` | number | No | Axle position from the front of the cargo area. Unit: `cm`. |\n| `load` | number | No | Cargo load on the axle. Unit: `kg`. |\n| `max` | number | No | Configured maximum (0 = none). Unit: `kg`. |\n| `ok` | boolean | No | `load` ≤ `max` (always `true` without a maximum). |\n\n### UnplacedItem\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id. |\n| `instance` | integer | No | 1-based unit number. |\n| `label` | string | No | Item label. |\n| `reason` | string | No | Why it could not be placed. |\n\n\n## Related\n\n- [Get a job result](https://runner.cargoduo.com/api/public/api-docs/pages/get-job-result.md): The load plan of a succeeded job: every vehicle used, every placement, what did not fit and the PDF link.\n- [Cancel a job](https://runner.cargoduo.com/api/public/api-docs/pages/cancel-job.md): Stops a queued or running job.\n- [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md): One error envelope, stable codes, clear retry rules.\n"},{"slug":"get-job-result","title":"Get a job result","section":"API reference","group":"Jobs","summary":"The load plan of a succeeded job: every vehicle used, every placement, what did not fit and the PDF link.","keywords":"get /v1/jobs/{id}/result getjobresult","method":"GET","path":"/v1/jobs/{id}/result","operation_id":"getJobResult","related":["results","pdf-reports","get-job"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/get-job-result.md","markdown":"# Get a job result\n\nThe load plan of a succeeded job: every vehicle used, every placement, what did not fit and the PDF link.\n\n```http\nGET https://runner.cargoduo.com/v1/jobs/{id}/result\n```\n\n## Authentication\n\nBearer API key with the `jobs:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Job id (job_…). |\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/jobs/job_4bK9mT2xQe7LpW1zRc8VdN3f/result\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/jobs/job_4bK9mT2xQe7LpW1zRc8VdN3f/result\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/jobs/job_4bK9mT2xQe7LpW1zRc8VdN3f/result\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"job_id\": \"job_4bK9mT2xQe7LpW1zRc8VdN3f\",\n  \"vehicles\": [\n    {\n      \"index\": 0,\n      \"vehicle_id\": \"trailer\",\n      \"copy\": 1,\n      \"auto_added\": false,\n      \"name\": \"13.6 m trailer\",\n      \"type\": \"trailer\",\n      \"length\": 1360,\n      \"width\": 245,\n      \"height\": 270,\n      \"max_weight\": 24000,\n      \"items_placed\": 58,\n      \"total_weight\": 8580,\n      \"volume_utilization\": 0.3116,\n      \"weight_utilization\": 0.3575,\n      \"center_of_gravity\": {\n        \"x\": 452.6,\n        \"y\": 68.4,\n        \"z\": 121.8\n      },\n      \"axle_loads\": [],\n      \"placements\": [\n        {\n          \"item_id\": \"pallet-eu\",\n          \"instance\": 1,\n          \"label\": \"Euro pallet\",\n          \"x\": 0,\n          \"y\": 0,\n          \"z\": 0,\n          \"length\": 80,\n          \"width\": 120,\n          \"height\": 140,\n          \"rotated\": true,\n          \"orientation\": \"wlh\",\n          \"step\": 1,\n          \"weight\": 450\n        },\n        {\n          \"item_id\": \"pallet-eu\",\n          \"instance\": 2,\n          \"label\": \"Euro pallet\",\n          \"x\": 0,\n          \"y\": 0,\n          \"z\": 120,\n          \"length\": 80,\n          \"width\": 120,\n          \"height\": 140,\n          \"rotated\": true,\n          \"orientation\": \"wlh\",\n          \"step\": 2,\n          \"weight\": 450\n        },\n        {\n          \"item_id\": \"carton-a\",\n          \"instance\": 1,\n          \"label\": \"Carton A\",\n          \"x\": 720,\n          \"y\": 0,\n          \"z\": 0,\n          \"length\": 60,\n          \"width\": 40,\n          \"height\": 40,\n          \"rotated\": false,\n          \"orientation\": \"lwh\",\n          \"step\": 19,\n          \"weight\": 12\n        }\n      ]\n    }\n  ],\n  \"unplaced\": [],\n  \"warnings\": [],\n  \"pdf_url\": \"https://app.cargoduo.com/r/Xq7Lm2Vt9KbR4cN8wZp1Hs6Yd3Fg5Jk0Ta2Ue7Wn4Ci\",\n  \"expires_at\": \"2026-11-02T09:12:03Z\"\n}\n```\n\nAbridged: 3 of the 58 placements are shown.\n\n## Responses\n\n### 200 OK\n\nThe load plan. Returns `Result`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `job_not_found` | No job with this id in your organization. |\n\n### 409 Conflict\n\n| Code | Meaning |\n|---|---|\n| `job_not_finished` | The job is still queued or running. |\n| `job_failed` | The job failed; it has no result. |\n| `job_canceled` | The job was canceled; it has no result. |\n\n### 410 Gone\n\n| Code | Meaning |\n|---|---|\n| `result_expired` | The result passed the retention window and was deleted. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Result object\n\nThe load plan. Coordinates are centimetres from the min corner of the cargo space: x along the length (0 = front wall, growing toward the door), y up from the floor, z across the width (0 = the right wall as seen standing at the door looking in, i.e. the driver's right; z grows toward the left wall).\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `job_id` | string | No | The job. |\n| `vehicles` | ResultVehicle[] | No | Loaded vehicles in loading order (empty vehicles are left out). |\n| `unplaced` | UnplacedItem[] | No | Units that could not be placed, with the reason. |\n| `warnings` | Warning[] | No | Remarks about the plan. |\n| `pdf_url` | string | No | Browser link that renders the plan as a PDF. |\n| `expires_at` | string | No | When this result is deleted (RFC 3339). |\n\n### ResultVehicle\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `index` | integer | No | 0-based position in the plan. |\n| `vehicle_id` | string | No | Your vehicle definition id (for a project job: the slot id, pv_…). |\n| `copy` | integer | No | 1-based copy number of that definition (quantity \u003e 1, or added in auto mode). |\n| `auto_added` | boolean | No | Added by `packing_mode` `auto` beyond the `quantity` you listed. |\n| `name` | string | No | Vehicle name. |\n| `type` | string | No | Vehicle type. |\n| `length` | number | No | Cargo-space length. Unit: `cm`. |\n| `width` | number | No | Cargo-space width. Unit: `cm`. |\n| `height` | number | No | Cargo-space height. Unit: `cm`. |\n| `max_weight` | number | No | Payload limit. Unit: `kg`. |\n| `items_placed` | integer | No | Units in this vehicle. |\n| `total_weight` | number | No | Weight in this vehicle. Unit: `kg`. |\n| `volume_utilization` | number | No | Used volume / cargo-space volume, 0–1. |\n| `weight_utilization` | number | No | total_weight / max_weight, 0–1 (may exceed 1 with allow_overweight). |\n| `center_of_gravity` | Point | No | Centre of gravity of the load (cm). |\n| `placements` | Placement[] | No | Every unit in this vehicle. |\n| `axle_loads` | AxleLoad[] | No | Load per axle (vehicles with axle data only). Requires access (`axle_loads`). |\n\n### Point\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | No | Unit: `cm`. |\n| `y` | number | No | Unit: `cm`. |\n| `z` | number | No | Unit: `cm`. |\n\n### Placement\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id (for a project job: the item id, item_…). |\n| `instance` | integer | No | 1-based unit number within that item definition (unique across the whole plan). |\n| `label` | string | No | Item label. |\n| `x` | number | No | Min-corner position along the length. Unit: `cm`. |\n| `y` | number | No | Min-corner height above the floor. Unit: `cm`. |\n| `z` | number | No | Min-corner position across the width. Unit: `cm`. |\n| `length` | number | No | Size along x as placed. Unit: `cm`. |\n| `width` | number | No | Size along z as placed. Unit: `cm`. |\n| `height` | number | No | Size along y as placed. Unit: `cm`. |\n| `rotated` | boolean | No | Placed in a different orientation than defined. |\n| `orientation` | string | No | Which defined side lies along x, z and y: `lwh` = as defined, `wlh` = turned 90° around the vertical axis, `lhw`, `hwl`, `whl`, `hlw` = tipped onto a side. |\n| `step` | integer | No | 1-based loading step within the vehicle (load in ascending order). |\n| `weight` | number | No | Unit weight. Unit: `kg`. |\n\n### AxleLoad\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | No | `Axle 1`, `Axle 2`… in the order of `axle_positions`. |\n| `position` | number | No | Axle position from the front of the cargo area. Unit: `cm`. |\n| `load` | number | No | Cargo load on the axle. Unit: `kg`. |\n| `max` | number | No | Configured maximum (0 = none). Unit: `kg`. |\n| `ok` | boolean | No | `load` ≤ `max` (always `true` without a maximum). |\n\n### UnplacedItem\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id. |\n| `instance` | integer | No | 1-based unit number. |\n| `label` | string | No | Item label. |\n| `reason` | string | No | Why it could not be placed. |\n\n### Warning\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code. |\n| `message` | string | No | English explanation. |\n\n\n## Related\n\n- [Results \u0026 coordinates](https://runner.cargoduo.com/api/public/api-docs/pages/results.md): Where every item sits, in which orientation and loading order.\n- [PDF reports](https://runner.cargoduo.com/api/public/api-docs/pages/pdf-reports.md): A shareable link that builds the loading plan PDF.\n- [Get a job](https://runner.cargoduo.com/api/public/api-docs/pages/get-job.md): A job's status, progress, summary and cost.\n"},{"slug":"cancel-job","title":"Cancel a job","section":"API reference","group":"Jobs","summary":"Stops a queued or running job.","keywords":"delete /v1/jobs/{id} canceljob","method":"DELETE","path":"/v1/jobs/{id}","operation_id":"cancelJob","related":["credits","get-job"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/cancel-job.md","markdown":"# Cancel a job\n\nStops a queued or running job.\n\n```http\nDELETE https://runner.cargoduo.com/v1/jobs/{id}\n```\n\nA canceled job pays the per-job fee, any large-load surcharge and the compute used so far, never the per-vehicle fee. Canceling a canceled job returns it unchanged.\n\n## Authentication\n\nBearer API key with the `pack:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Job id (job_…). |\n\n## Example request\n\n```bash\ncurl -X DELETE \"https://runner.cargoduo.com/v1/jobs/job_2cR8vL4nXp6tQb1mZk9WsH5e\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/jobs/job_2cR8vL4nXp6tQb1mZk9WsH5e\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.delete(\n    \"https://runner.cargoduo.com/v1/jobs/job_2cR8vL4nXp6tQb1mZk9WsH5e\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"id\": \"job_2cR8vL4nXp6tQb1mZk9WsH5e\",\n  \"object\": \"job\",\n  \"status\": \"canceled\",\n  \"reference\": \"order-1043\",\n  \"source\": \"api\",\n  \"created_at\": \"2026-10-19T09:15:03Z\",\n  \"started_at\": \"2026-10-19T09:15:03Z\",\n  \"finished_at\": \"2026-10-19T09:15:05Z\",\n  \"progress\": {\n    \"stage\": \"done\",\n    \"vehicles_done\": 0,\n    \"placed_items\": 0\n  },\n  \"summary\": {\n    \"items_requested\": 58,\n    \"items_placed\": 0,\n    \"items_unplaced\": 0,\n    \"vehicles_used\": 0,\n    \"total_weight\": 0,\n    \"volume_utilization\": 0,\n    \"queue_ms\": 15,\n    \"compute_ms\": 2300\n  },\n  \"cost\": {\n    \"credits\": 0.0715,\n    \"reserved_credits\": 1.11,\n    \"rate_card_version\": 1,\n    \"breakdown\": {\n      \"base\": 0.06,\n      \"vehicles\": 0,\n      \"compute\": 0.0115,\n      \"large_load\": 0\n    }\n  },\n  \"error\": {\n    \"code\": \"canceled\",\n    \"message\": \"Canceled by request.\"\n  },\n  \"warnings\": [],\n  \"result_url\": \"https://runner.cargoduo.com/v1/jobs/job_2cR8vL4nXp6tQb1mZk9WsH5e/result\",\n  \"pdf_url\": \"\",\n  \"expires_at\": \"2026-11-02T09:15:03Z\"\n}\n```\n\n## Responses\n\n### 200 OK\n\nThe canceled job. Returns `Job`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `job_not_found` | No job with this id in your organization. |\n\n### 409 Conflict\n\n| Code | Meaning |\n|---|---|\n| `job_already_finished` | The job already ended and cannot be canceled. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Job object\n\nA packing job. Created by POST /v1/pack; poll GET /v1/jobs/{id} until `status` is `succeeded`, `failed` or `canceled`.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Job id (job_…). |\n| `object` | string | Yes | Always `job`. One of: `job`. |\n| `status` | string | Yes | `queued` → `running` → `succeeded` \\| `failed` \\| `canceled`. One of: `queued`, `running`, `succeeded`, `failed`, `canceled`. |\n| `reference` | string | No | Your reference from the request. |\n| `source` | string | No | Where the job was created. One of: `api`, `playground`. |\n| `project_id` | string | No | The project (prj_…) a POST /v1/projects/{id}/pack job saves its plan into. Absent on other jobs. |\n| `created_at` | string | Yes | RFC 3339 timestamp. |\n| `started_at` | string \\| null | No | When computing started (RFC 3339) or `null`. |\n| `finished_at` | string \\| null | No | When the job ended (RFC 3339) or `null`. |\n| `progress` | JobProgress | No | Live progress while queued/running. |\n| `summary` | JobSummary | No | Counts and timings. |\n| `cost` | JobCost | No | Credits charged (final once the job ended) and the reservation held while it ran. |\n| `error` | JobError \\| null | No | Why the job failed or was canceled; `null` otherwise. |\n| `warnings` | Warning[] | No | Non-blocking remarks about the request (also repeated in the result). |\n| `result_url` | string | No | GET this URL for the full result once `status` is `succeeded`. |\n| `pdf_url` | string | No | A browser link that renders the load plan as a PDF (3D views + loading steps). Valid until expires_at. Empty after expiry. |\n| `expires_at` | string | No | When the request/result bodies and the PDF link are deleted (RFC 3339). Job metadata and charges are kept. |\n| `result` | Result | No | The full result, inlined only when requested (?include=result) or when POST /v1/pack?wait= finished in time. |\n\n### JobProgress\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `stage` | string | No | Current stage, such as `queued`, `running`, `finalizing` or `done`. Informational: branch on `Job.status`. |\n| `vehicles_done` | integer | No | Vehicles packed so far. |\n| `placed_items` | integer | No | Item units placed so far. |\n\n### JobSummary\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `items_requested` | integer | No | Total item units (sum of quantities). |\n| `items_placed` | integer | No | Units placed. |\n| `items_unplaced` | integer | No | Units that could not be placed. |\n| `vehicles_used` | integer | No | Vehicles that carry at least one item. |\n| `total_weight` | number | No | Weight loaded. Unit: `kg`. |\n| `volume_utilization` | number | No | Average volume utilization of the used vehicles, 0–1. |\n| `queue_ms` | integer | No | Time spent waiting for a compute slot (not billed). |\n| `compute_ms` | integer | No | Compute time (billed). |\n\n### JobCost\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `credits` | number | No | Credits charged (0 until the job ends). Failed jobs are not charged, except time_limit_exceeded, which is charged like a canceled job. Unit: `credits`. |\n| `reserved_credits` | number | No | Credits held while the job was queued/running (the worst-case price). Released when it ends. Unit: `credits`. |\n| `rate_card_version` | integer | No | Rate card the job is priced with. |\n| `breakdown` | CostBreakdown | No | Itemized price. |\n\n### CostBreakdown\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `base` | number | No | Per-job fee. Unit: `credits`. |\n| `vehicles` | number | No | Per loaded vehicle (not charged for canceled or time_limit_exceeded jobs). Unit: `credits`. |\n| `compute` | number | No | Per compute-second, pro-rated per millisecond. Unit: `credits`. |\n| `large_load` | number | No | Surcharge per started 100 items above the large-load threshold. Unit: `credits`. |\n\n### JobError\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code (see Errors). |\n| `message` | string | No | English explanation. |\n\n### Warning\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code. |\n| `message` | string | No | English explanation. |\n\n### Result\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `job_id` | string | No | The job. |\n| `vehicles` | ResultVehicle[] | No | Loaded vehicles in loading order (empty vehicles are left out). |\n| `unplaced` | UnplacedItem[] | No | Units that could not be placed, with the reason. |\n| `warnings` | Warning[] | No | Remarks about the plan. |\n| `pdf_url` | string | No | Browser link that renders the plan as a PDF. |\n| `expires_at` | string | No | When this result is deleted (RFC 3339). |\n\n### ResultVehicle\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `index` | integer | No | 0-based position in the plan. |\n| `vehicle_id` | string | No | Your vehicle definition id (for a project job: the slot id, pv_…). |\n| `copy` | integer | No | 1-based copy number of that definition (quantity \u003e 1, or added in auto mode). |\n| `auto_added` | boolean | No | Added by `packing_mode` `auto` beyond the `quantity` you listed. |\n| `name` | string | No | Vehicle name. |\n| `type` | string | No | Vehicle type. |\n| `length` | number | No | Cargo-space length. Unit: `cm`. |\n| `width` | number | No | Cargo-space width. Unit: `cm`. |\n| `height` | number | No | Cargo-space height. Unit: `cm`. |\n| `max_weight` | number | No | Payload limit. Unit: `kg`. |\n| `items_placed` | integer | No | Units in this vehicle. |\n| `total_weight` | number | No | Weight in this vehicle. Unit: `kg`. |\n| `volume_utilization` | number | No | Used volume / cargo-space volume, 0–1. |\n| `weight_utilization` | number | No | total_weight / max_weight, 0–1 (may exceed 1 with allow_overweight). |\n| `center_of_gravity` | Point | No | Centre of gravity of the load (cm). |\n| `placements` | Placement[] | No | Every unit in this vehicle. |\n| `axle_loads` | AxleLoad[] | No | Load per axle (vehicles with axle data only). Requires access (`axle_loads`). |\n\n### Point\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | No | Unit: `cm`. |\n| `y` | number | No | Unit: `cm`. |\n| `z` | number | No | Unit: `cm`. |\n\n### Placement\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id (for a project job: the item id, item_…). |\n| `instance` | integer | No | 1-based unit number within that item definition (unique across the whole plan). |\n| `label` | string | No | Item label. |\n| `x` | number | No | Min-corner position along the length. Unit: `cm`. |\n| `y` | number | No | Min-corner height above the floor. Unit: `cm`. |\n| `z` | number | No | Min-corner position across the width. Unit: `cm`. |\n| `length` | number | No | Size along x as placed. Unit: `cm`. |\n| `width` | number | No | Size along z as placed. Unit: `cm`. |\n| `height` | number | No | Size along y as placed. Unit: `cm`. |\n| `rotated` | boolean | No | Placed in a different orientation than defined. |\n| `orientation` | string | No | Which defined side lies along x, z and y: `lwh` = as defined, `wlh` = turned 90° around the vertical axis, `lhw`, `hwl`, `whl`, `hlw` = tipped onto a side. |\n| `step` | integer | No | 1-based loading step within the vehicle (load in ascending order). |\n| `weight` | number | No | Unit weight. Unit: `kg`. |\n\n### AxleLoad\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | No | `Axle 1`, `Axle 2`… in the order of `axle_positions`. |\n| `position` | number | No | Axle position from the front of the cargo area. Unit: `cm`. |\n| `load` | number | No | Cargo load on the axle. Unit: `kg`. |\n| `max` | number | No | Configured maximum (0 = none). Unit: `kg`. |\n| `ok` | boolean | No | `load` ≤ `max` (always `true` without a maximum). |\n\n### UnplacedItem\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id. |\n| `instance` | integer | No | 1-based unit number. |\n| `label` | string | No | Item label. |\n| `reason` | string | No | Why it could not be placed. |\n\n\n## Related\n\n- [Credits \u0026 pricing](https://runner.cargoduo.com/api/public/api-docs/pages/credits.md): What a job costs and where credits come from.\n- [Get a job](https://runner.cargoduo.com/api/public/api-docs/pages/get-job.md): A job's status, progress, summary and cost.\n"},{"slug":"list-projects","title":"List projects","section":"API reference","group":"Projects","summary":"Your organization's projects (the app's load plans), newest first, one page at a time.","keywords":"get /v1/projects listprojects","method":"GET","path":"/v1/projects","operation_id":"listProjects","related":["get-project","saved-data"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/list-projects.md","markdown":"# List projects\n\nYour organization's projects (the app's load plans), newest first, one page at a time.\n\n```http\nGET https://runner.cargoduo.com/v1/projects\n```\n\nFree: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Query parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `limit` | integer | No | Page size. Default: `20`. 1–100. |\n| `cursor` | string | No | next_cursor of the previous page. |\n| `q` | string | No | Only projects whose name contains this text. |\n| `created_by` | string | No | Only what this member created: `me` (the key's creator) or a member id (`usr_…`). |\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/projects?limit=2\u0026created_by=me\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/projects?limit=2\u0026created_by=me\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/projects\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    params={\n        \"limit\": 2,\n        \"created_by\": \"me\",\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"prj_3051\",\n      \"object\": \"project\",\n      \"name\": \"Order 1042\",\n      \"status\": \"calculated\",\n      \"item_count\": 58,\n      \"vehicle_count\": 1,\n      \"app_url\": \"https://app.cargoduo.com/projects/3051\",\n      \"created_by\": {\n        \"id\": \"usr_7\",\n        \"name\": \"Elif Kaya\"\n      },\n      \"updated_by\": {\n        \"id\": \"usr_7\",\n        \"name\": \"Elif Kaya\"\n      },\n      \"created_at\": \"2026-10-19T09:10:03Z\",\n      \"updated_at\": \"2026-10-19T09:12:07Z\"\n    },\n    {\n      \"id\": \"prj_3048\",\n      \"object\": \"project\",\n      \"name\": \"Order 1039\",\n      \"status\": \"draft\",\n      \"item_count\": 24,\n      \"vehicle_count\": 1,\n      \"app_url\": \"https://app.cargoduo.com/projects/3048\",\n      \"created_by\": {\n        \"id\": \"usr_7\",\n        \"name\": \"Elif Kaya\"\n      },\n      \"updated_by\": {\n        \"id\": \"usr_7\",\n        \"name\": \"Elif Kaya\"\n      },\n      \"created_at\": \"2026-10-17T07:12:03Z\",\n      \"updated_at\": \"2026-10-17T07:12:03Z\"\n    }\n  ],\n  \"next_cursor\": \"prj_3048\"\n}\n```\n\n## Responses\n\n### 200 OK\n\nOne page of projects. Returns `ProjectList`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The ProjectList object\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `data` | ProjectSummary[] | No | Newest first. |\n| `next_cursor` | string | No | Pass as ?cursor= for the next page; empty on the last page. |\n\n### ProjectSummary\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Project id (prj_…). |\n| `object` | string | No | One of: `project`. |\n| `name` | string | No |  |\n| `status` | string | No | `draft` = never packed or inputs replaced; `calculated` = has a current plan; `stale` = a setting changed since the plan was made. One of: `draft`, `calculated`, `stale`, `error`. |\n| `item_count` | integer | No | Item units (sum of quantities). |\n| `vehicle_count` | integer | No | Vehicles (sum of quantities). |\n| `app_url` | string | No | Opens the project in the app's 3D editor. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string | No | RFC 3339. |\n| `updated_at` | string | No | RFC 3339. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n\n## Related\n\n- [Get a project](https://runner.cargoduo.com/api/public/api-docs/pages/get-project.md): A project with its vehicles, items, groups, options and a summary of its current plan (with the PDF link while the plan is the one an API job saved).\n- [Saved data \u0026 projects](https://runner.cargoduo.com/api/public/api-docs/pages/saved-data.md): Vehicles, templates and projects shared with the app.\n"},{"slug":"create-project","title":"Create a project","section":"API reference","group":"Projects","summary":"Creates a project that opens in the app: a name, and optionally vehicles (saved vehicles by vehicle_id), items and groups in the /v1/pack shapes and the stored options.","keywords":"post /v1/projects createproject","method":"POST","path":"/v1/projects","operation_id":"createProject","related":["pack-project","list-vehicles","list-templates"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/create-project.md","markdown":"# Create a project\n\nCreates a project that opens in the app: a name, and optionally vehicles (saved vehicles by vehicle_id), items and groups in the /v1/pack shapes and the stored options.\n\n```http\nPOST https://runner.cargoduo.com/v1/projects\n```\n\nPack it with POST /v1/projects/{id}/pack. Free: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Body parameters\n\nA new project. Items take the /v1/pack `Item` shape (`template_id` works; `id` is optional and only fills `label`). Groups an item names but the list leaves out are created.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | Yes | Max 255 characters. |\n| `vehicles` | ProjectVehicle[] | No | Vehicle slots. |\n| `items` | Item[] | No | Cargo items (at most 500 definitions). |\n| `groups` | Group[] | No | Groups, with their loading order. |\n| `options` | ProjectOptions | No |  |\n\n### ProjectVehicle\n\nA vehicle slot of a project: a saved vehicle, a quantity and the overrides the app's project editor offers. `id`, `name` and `type` are read-only (ignored when sent). `length`, `width`, `height` and `max_weight` show the sizes the slot packs with; sent, they override only where they differ from the saved vehicle.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Slot id (pv_…). Results of the project's jobs refer to it as vehicle_id. Read-only. |\n| `vehicle_id` | string | Yes | A vehicle id from GET /v1/vehicles (preset or veh_…). |\n| `name` | string | No | The saved vehicle's name. Read-only. |\n| `type` | string | No | The saved vehicle's type. Read-only. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `length` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `width` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `height` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `max_weight` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `kg`. |\n| `quantity` | integer | No | How many identical vehicles of this definition are available. Default: `1`. 1–100. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). Default: `false`. |\n| `aux_deck_height` | number | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. At most 32 entries. |\n| `tractor_weight` | number | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `empty_trailer_weight` | number | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `front_axle_to_middle_axle` | number | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_tractor_cog` | number | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_kingpin` | number | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. Requires access (`axle_loads`). |\n| `kingpin_to_rear_axle` | number | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_cog_to_rear_axle` | number | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_start_to_kingpin` | number | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. Requires access (`axle_loads`). |\n| `max_middle_axle_weight` | number | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. Requires access (`axle_loads`). |\n| `max_rear_axle_weight` | number | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. Requires access (`axle_loads`). |\n\n### Rect\n\nA rectangle on the vehicle floor (min-corner origin).\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n### Item\n\nAn item definition (one SKU / package type). `quantity` identical units are loaded; each unit is reported as one placement with an `instance` number. Name a saved template with `template_id` and send only what differs.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Your identifier, unique within the request. Results refer to it as item_id. Defaults to template_id. Max 128 characters. |\n| `template_id` | string | No | A template id (tpl_…) from GET /v1/templates. Supplies every item field; fields sent next to it override them. |\n| `label` | string | No | Display label (defaults to the id). Max 255 characters. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. Default: `\"box\"`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | Yes | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | Yes | Weight of ONE unit. Unit: `kg`. |\n| `quantity` | integer | No | Number of identical units. Default: `1`. 1–10000. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. Default: `true`. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Default: `0`. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. Default: `\"all\"`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. Default: `false`. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. Default: `0`. |\n| `group` | string | No | Name of the group this item belongs to (see `groups` and `options.group_routing`). |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Default: `0`. Unit: `cm`. |\n\n### Group\n\nA group of items (a customer, a stop, an order). With group routing, group order decides the loading sequence: order 1 is loaded first (deepest, at the front wall), the highest order last (at the door). Items without a group go last.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | Yes | Group name, referenced by Item.group. Max 128 characters. |\n| `order` | integer | No | Loading order (lower = loaded first). Defaults to the position in the list. |\n| `color` | string | No | Display colour as #rrggbb. |\n\n### ProjectOptions\n\nThe pack options a project stores.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `packing_mode` | string | No | `auto` = when the listed vehicles are full, more copies of the most suitable vehicle are added (up to the job's vehicle limit); `fleet_only` = only the listed vehicles are used, anything that does not fit is reported as unplaced. One of: `auto`, `fleet_only`. Default: `\"auto\"`. |\n| `group_routing` | string | No | `none` = groups are informational; `wall` = each group gets its own section of the vehicle; `routing` = groups load in order and a later group may sit on or beside an earlier one but never below it (multi-stop delivery); `no_stack` = groups load in order side by side, never on top of each other. One of: `none`, `wall`, `routing`, `no_stack`. Default: `\"none\"`. |\n| `allow_overweight` | boolean | No | Ignore vehicle payload limits. Default: `false`. |\n\n## Example request\n\n```bash\ncurl -X POST \"https://runner.cargoduo.com/v1/projects\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Order 1042\",\n    \"vehicles\": [\n      {\n        \"vehicle_id\": \"standard-trailer-13-6m\"\n      }\n    ],\n    \"items\": [\n      {\n        \"template_id\": \"tpl_42\",\n        \"quantity\": 18\n      },\n      {\n        \"template_id\": \"tpl_43\",\n        \"quantity\": 40\n      }\n    ]\n  }'\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/projects\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"name\": \"Order 1042\",\n    \"vehicles\": [\n      {\n        \"vehicle_id\": \"standard-trailer-13-6m\"\n      }\n    ],\n    \"items\": [\n      {\n        \"template_id\": \"tpl_42\",\n        \"quantity\": 18\n      },\n      {\n        \"template_id\": \"tpl_43\",\n        \"quantity\": 40\n      }\n    ]\n  }),\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.post(\n    \"https://runner.cargoduo.com/v1/projects\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    json={\n        \"name\": \"Order 1042\",\n        \"vehicles\": [\n            {\n                \"vehicle_id\": \"standard-trailer-13-6m\",\n            },\n        ],\n        \"items\": [\n            {\n                \"template_id\": \"tpl_42\",\n                \"quantity\": 18,\n            },\n            {\n                \"template_id\": \"tpl_43\",\n                \"quantity\": 40,\n            },\n        ],\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`201` \n\n```json\n{\n  \"id\": \"prj_3051\",\n  \"object\": \"project\",\n  \"name\": \"Order 1042\",\n  \"status\": \"draft\",\n  \"item_count\": 58,\n  \"vehicle_count\": 1,\n  \"app_url\": \"https://app.cargoduo.com/projects/3051\",\n  \"created_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"updated_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"created_at\": \"2026-10-19T09:10:03Z\",\n  \"updated_at\": \"2026-10-19T09:10:03Z\",\n  \"options\": {\n    \"packing_mode\": \"auto\",\n    \"group_routing\": \"none\",\n    \"allow_overweight\": false\n  },\n  \"vehicles\": [\n    {\n      \"id\": \"pv_5120\",\n      \"vehicle_id\": \"standard-trailer-13-6m\",\n      \"name\": \"Standard Trailer (13.6m)\",\n      \"type\": \"trailer\",\n      \"quantity\": 1,\n      \"length\": 1362,\n      \"width\": 248,\n      \"height\": 270,\n      \"max_weight\": 24000\n    }\n  ],\n  \"items\": [\n    {\n      \"id\": \"item_88410\",\n      \"label\": \"Euro pallet\",\n      \"shape\": \"box\",\n      \"length\": 120,\n      \"width\": 80,\n      \"height\": 140,\n      \"weight\": 450,\n      \"quantity\": 18,\n      \"stackable\": false,\n      \"rotation\": \"all\",\n      \"color\": \"#3b82f6\"\n    },\n    {\n      \"id\": \"item_88411\",\n      \"label\": \"Carton A\",\n      \"shape\": \"box\",\n      \"length\": 60,\n      \"width\": 40,\n      \"height\": 40,\n      \"weight\": 12,\n      \"quantity\": 40,\n      \"stackable\": true,\n      \"rotation\": \"upright\",\n      \"color\": \"#f59e0b\"\n    }\n  ],\n  \"groups\": [],\n  \"plan\": null\n}\n```\n\n## Responses\n\n### 201 \n\nThe new project. Returns `Project`.\n\n### 400 Bad request\n\n| Code | Meaning |\n|---|---|\n| `invalid_json` | The body is not valid JSON or a field has the wrong type. |\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `plan_limit_reached` | The organization's plan allows no more projects or cargo items. |\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 413 Payload too large\n\n| Code | Meaning |\n|---|---|\n| `payload_too_large` | The request body exceeds 10 MB. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Project object\n\nA load plan of your organization: the same project the app opens in its 3D editor.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Project id (prj_…). |\n| `object` | string | No | One of: `project`. |\n| `name` | string | No |  |\n| `status` | string | No | `draft` = never packed or inputs replaced; `calculated` = has a current plan; `stale` = a setting changed since the plan was made. One of: `draft`, `calculated`, `stale`, `error`. |\n| `item_count` | integer | No | Item units (sum of quantities). |\n| `vehicle_count` | integer | No | Vehicles (sum of quantities). |\n| `app_url` | string | No | Opens the project in the app's 3D editor. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string | No | RFC 3339. |\n| `updated_at` | string | No | RFC 3339. |\n| `options` | ProjectOptions | No |  |\n| `vehicles` | ProjectVehicle[] | No |  |\n| `items` | Item[] | No | Cargo in the /v1/pack Item shape; ids are item_…. |\n| `groups` | Group[] | No |  |\n| `plan` | ProjectPlan \\| null | No | The current plan; `null` when the project has none. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n### ProjectOptions\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `packing_mode` | string | No | `auto` = when the listed vehicles are full, more copies of the most suitable vehicle are added (up to the job's vehicle limit); `fleet_only` = only the listed vehicles are used, anything that does not fit is reported as unplaced. One of: `auto`, `fleet_only`. Default: `\"auto\"`. |\n| `group_routing` | string | No | `none` = groups are informational; `wall` = each group gets its own section of the vehicle; `routing` = groups load in order and a later group may sit on or beside an earlier one but never below it (multi-stop delivery); `no_stack` = groups load in order side by side, never on top of each other. One of: `none`, `wall`, `routing`, `no_stack`. Default: `\"none\"`. |\n| `allow_overweight` | boolean | No | Ignore vehicle payload limits. Default: `false`. |\n\n### ProjectVehicle\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Slot id (pv_…). Results of the project's jobs refer to it as vehicle_id. Read-only. |\n| `vehicle_id` | string | Yes | A vehicle id from GET /v1/vehicles (preset or veh_…). |\n| `name` | string | No | The saved vehicle's name. Read-only. |\n| `type` | string | No | The saved vehicle's type. Read-only. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `length` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `width` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `height` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `max_weight` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `kg`. |\n| `quantity` | integer | No | How many identical vehicles of this definition are available. Default: `1`. 1–100. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). Default: `false`. |\n| `aux_deck_height` | number | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. At most 32 entries. |\n| `tractor_weight` | number | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `empty_trailer_weight` | number | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `front_axle_to_middle_axle` | number | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_tractor_cog` | number | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_kingpin` | number | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. Requires access (`axle_loads`). |\n| `kingpin_to_rear_axle` | number | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_cog_to_rear_axle` | number | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_start_to_kingpin` | number | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. Requires access (`axle_loads`). |\n| `max_middle_axle_weight` | number | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. Requires access (`axle_loads`). |\n| `max_rear_axle_weight` | number | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. Requires access (`axle_loads`). |\n\n### Rect\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n### Item\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Your identifier, unique within the request. Results refer to it as item_id. Defaults to template_id. Max 128 characters. |\n| `template_id` | string | No | A template id (tpl_…) from GET /v1/templates. Supplies every item field; fields sent next to it override them. |\n| `label` | string | No | Display label (defaults to the id). Max 255 characters. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. Default: `\"box\"`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | Yes | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | Yes | Weight of ONE unit. Unit: `kg`. |\n| `quantity` | integer | No | Number of identical units. Default: `1`. 1–10000. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. Default: `true`. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Default: `0`. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. Default: `\"all\"`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. Default: `false`. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. Default: `0`. |\n| `group` | string | No | Name of the group this item belongs to (see `groups` and `options.group_routing`). |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Default: `0`. Unit: `cm`. |\n\n### Group\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | Yes | Group name, referenced by Item.group. Max 128 characters. |\n| `order` | integer | No | Loading order (lower = loaded first). Defaults to the position in the list. |\n| `color` | string | No | Display colour as #rrggbb. |\n\n### ProjectPlan\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `items_placed` | integer | No | Units placed. |\n| `items_unplaced` | integer | No | Units that could not be placed. |\n| `vehicles_used` | integer | No | Vehicles carrying at least one item. |\n| `total_weight` | number | No | Weight loaded. Unit: `kg`. |\n| `volume_utilization` | number | No | Average volume utilization of the used vehicles, 0–1. |\n| `job_id` | string | No | The API job whose plan this is, while nothing changed it since. Empty for a plan made or edited in the app. |\n| `pdf_url` | string | No | That job's PDF link (until its expires_at). Empty otherwise. |\n\n\n## Related\n\n- [Pack a project](https://runner.cargoduo.com/api/public/api-docs/pages/pack-project.md): Packs the project's vehicles and items exactly like POST /v1/pack: same price, credit reservation, limits, Idempotency-Key, ?wait= and job.\n- [List vehicles](https://runner.cargoduo.com/api/public/api-docs/pages/list-vehicles.md): Every vehicle you can name with vehicle_id: the presets (containers, trailers, trucks, pallets of the types your organization may use), then your organization's own vehicles.\n- [List templates](https://runner.cargoduo.com/api/public/api-docs/pages/list-templates.md): Your organization's cargo templates (saved items), newest first.\n"},{"slug":"get-project","title":"Get a project","section":"API reference","group":"Projects","summary":"A project with its vehicles, items, groups, options and a summary of its current plan (with the PDF link while the plan is the one an API job saved).","keywords":"get /v1/projects/{id} getproject","method":"GET","path":"/v1/projects/{id}","operation_id":"getProject","related":["pack-project","update-project"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/get-project.md","markdown":"# Get a project\n\nA project with its vehicles, items, groups, options and a summary of its current plan (with the PDF link while the plan is the one an API job saved).\n\n```http\nGET https://runner.cargoduo.com/v1/projects/{id}\n```\n\nFree: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Project id (prj_…). |\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/projects/prj_3051\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/projects/prj_3051\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/projects/prj_3051\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"id\": \"prj_3051\",\n  \"object\": \"project\",\n  \"name\": \"Order 1042\",\n  \"status\": \"calculated\",\n  \"item_count\": 58,\n  \"vehicle_count\": 1,\n  \"app_url\": \"https://app.cargoduo.com/projects/3051\",\n  \"created_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"updated_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"created_at\": \"2026-10-19T09:10:03Z\",\n  \"updated_at\": \"2026-10-19T09:12:07Z\",\n  \"options\": {\n    \"packing_mode\": \"auto\",\n    \"group_routing\": \"none\",\n    \"allow_overweight\": false\n  },\n  \"vehicles\": [\n    {\n      \"id\": \"pv_5120\",\n      \"vehicle_id\": \"standard-trailer-13-6m\",\n      \"name\": \"Standard Trailer (13.6m)\",\n      \"type\": \"trailer\",\n      \"quantity\": 1,\n      \"length\": 1362,\n      \"width\": 248,\n      \"height\": 270,\n      \"max_weight\": 24000\n    }\n  ],\n  \"items\": [\n    {\n      \"id\": \"item_88410\",\n      \"label\": \"Euro pallet\",\n      \"shape\": \"box\",\n      \"length\": 120,\n      \"width\": 80,\n      \"height\": 140,\n      \"weight\": 450,\n      \"quantity\": 18,\n      \"stackable\": false,\n      \"rotation\": \"all\",\n      \"color\": \"#3b82f6\"\n    },\n    {\n      \"id\": \"item_88411\",\n      \"label\": \"Carton A\",\n      \"shape\": \"box\",\n      \"length\": 60,\n      \"width\": 40,\n      \"height\": 40,\n      \"weight\": 12,\n      \"quantity\": 40,\n      \"stackable\": true,\n      \"rotation\": \"upright\",\n      \"color\": \"#f59e0b\"\n    }\n  ],\n  \"groups\": [],\n  \"plan\": {\n    \"items_placed\": 58,\n    \"items_unplaced\": 0,\n    \"vehicles_used\": 1,\n    \"total_weight\": 8580,\n    \"volume_utilization\": 0.3116,\n    \"job_id\": \"job_4bK9mT2xQe7LpW1zRc8VdN3f\",\n    \"pdf_url\": \"https://app.cargoduo.com/r/Xq7Lm2Vt9KbR4cN8wZp1Hs6Yd3Fg5Jk0Ta2Ue7Wn4Ci\"\n  }\n}\n```\n\n## Responses\n\n### 200 OK\n\nThe project. Returns `Project`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `project_not_found` | No project with this id in your organization. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Project object\n\nA load plan of your organization: the same project the app opens in its 3D editor.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Project id (prj_…). |\n| `object` | string | No | One of: `project`. |\n| `name` | string | No |  |\n| `status` | string | No | `draft` = never packed or inputs replaced; `calculated` = has a current plan; `stale` = a setting changed since the plan was made. One of: `draft`, `calculated`, `stale`, `error`. |\n| `item_count` | integer | No | Item units (sum of quantities). |\n| `vehicle_count` | integer | No | Vehicles (sum of quantities). |\n| `app_url` | string | No | Opens the project in the app's 3D editor. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string | No | RFC 3339. |\n| `updated_at` | string | No | RFC 3339. |\n| `options` | ProjectOptions | No |  |\n| `vehicles` | ProjectVehicle[] | No |  |\n| `items` | Item[] | No | Cargo in the /v1/pack Item shape; ids are item_…. |\n| `groups` | Group[] | No |  |\n| `plan` | ProjectPlan \\| null | No | The current plan; `null` when the project has none. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n### ProjectOptions\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `packing_mode` | string | No | `auto` = when the listed vehicles are full, more copies of the most suitable vehicle are added (up to the job's vehicle limit); `fleet_only` = only the listed vehicles are used, anything that does not fit is reported as unplaced. One of: `auto`, `fleet_only`. Default: `\"auto\"`. |\n| `group_routing` | string | No | `none` = groups are informational; `wall` = each group gets its own section of the vehicle; `routing` = groups load in order and a later group may sit on or beside an earlier one but never below it (multi-stop delivery); `no_stack` = groups load in order side by side, never on top of each other. One of: `none`, `wall`, `routing`, `no_stack`. Default: `\"none\"`. |\n| `allow_overweight` | boolean | No | Ignore vehicle payload limits. Default: `false`. |\n\n### ProjectVehicle\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Slot id (pv_…). Results of the project's jobs refer to it as vehicle_id. Read-only. |\n| `vehicle_id` | string | Yes | A vehicle id from GET /v1/vehicles (preset or veh_…). |\n| `name` | string | No | The saved vehicle's name. Read-only. |\n| `type` | string | No | The saved vehicle's type. Read-only. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `length` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `width` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `height` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `max_weight` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `kg`. |\n| `quantity` | integer | No | How many identical vehicles of this definition are available. Default: `1`. 1–100. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). Default: `false`. |\n| `aux_deck_height` | number | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. At most 32 entries. |\n| `tractor_weight` | number | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `empty_trailer_weight` | number | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `front_axle_to_middle_axle` | number | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_tractor_cog` | number | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_kingpin` | number | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. Requires access (`axle_loads`). |\n| `kingpin_to_rear_axle` | number | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_cog_to_rear_axle` | number | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_start_to_kingpin` | number | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. Requires access (`axle_loads`). |\n| `max_middle_axle_weight` | number | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. Requires access (`axle_loads`). |\n| `max_rear_axle_weight` | number | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. Requires access (`axle_loads`). |\n\n### Rect\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n### Item\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Your identifier, unique within the request. Results refer to it as item_id. Defaults to template_id. Max 128 characters. |\n| `template_id` | string | No | A template id (tpl_…) from GET /v1/templates. Supplies every item field; fields sent next to it override them. |\n| `label` | string | No | Display label (defaults to the id). Max 255 characters. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. Default: `\"box\"`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | Yes | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | Yes | Weight of ONE unit. Unit: `kg`. |\n| `quantity` | integer | No | Number of identical units. Default: `1`. 1–10000. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. Default: `true`. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Default: `0`. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. Default: `\"all\"`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. Default: `false`. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. Default: `0`. |\n| `group` | string | No | Name of the group this item belongs to (see `groups` and `options.group_routing`). |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Default: `0`. Unit: `cm`. |\n\n### Group\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | Yes | Group name, referenced by Item.group. Max 128 characters. |\n| `order` | integer | No | Loading order (lower = loaded first). Defaults to the position in the list. |\n| `color` | string | No | Display colour as #rrggbb. |\n\n### ProjectPlan\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `items_placed` | integer | No | Units placed. |\n| `items_unplaced` | integer | No | Units that could not be placed. |\n| `vehicles_used` | integer | No | Vehicles carrying at least one item. |\n| `total_weight` | number | No | Weight loaded. Unit: `kg`. |\n| `volume_utilization` | number | No | Average volume utilization of the used vehicles, 0–1. |\n| `job_id` | string | No | The API job whose plan this is, while nothing changed it since. Empty for a plan made or edited in the app. |\n| `pdf_url` | string | No | That job's PDF link (until its expires_at). Empty otherwise. |\n\n\n## Related\n\n- [Pack a project](https://runner.cargoduo.com/api/public/api-docs/pages/pack-project.md): Packs the project's vehicles and items exactly like POST /v1/pack: same price, credit reservation, limits, Idempotency-Key, ?wait= and job.\n- [Update a project](https://runner.cargoduo.com/api/public/api-docs/pages/update-project.md): Renames a project, changes its options, or replaces its vehicles or its items (with groups).\n"},{"slug":"update-project","title":"Update a project","section":"API reference","group":"Projects","summary":"Renames a project, changes its options, or replaces its vehicles or its items (with groups).","keywords":"patch /v1/projects/{id} updateproject","method":"PATCH","path":"/v1/projects/{id}","operation_id":"updateProject","related":["get-project","pack-project"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/update-project.md","markdown":"# Update a project\n\nRenames a project, changes its options, or replaces its vehicles or its items (with groups).\n\n```http\nPATCH https://runner.cargoduo.com/v1/projects/{id}\n```\n\nReplacing vehicles or items clears the stored plan; open app sessions reload. Free: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Project id (prj_…). |\n\n## Body parameters\n\nWhat to change. `vehicles` replaces every slot; `items` replaces the cargo and the groups (send `groups` only together with `items`); both clear the stored plan. Changed `options` mark a plan `stale`.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | No | Max 255 characters. |\n| `vehicles` | ProjectVehicle[] | No | Vehicle slots. |\n| `items` | Item[] | No | Cargo items (at most 500 definitions). |\n| `groups` | Group[] | No | Groups, with their loading order. |\n| `options` | ProjectOptions | No |  |\n\n### ProjectVehicle\n\nA vehicle slot of a project: a saved vehicle, a quantity and the overrides the app's project editor offers. `id`, `name` and `type` are read-only (ignored when sent). `length`, `width`, `height` and `max_weight` show the sizes the slot packs with; sent, they override only where they differ from the saved vehicle.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Slot id (pv_…). Results of the project's jobs refer to it as vehicle_id. Read-only. |\n| `vehicle_id` | string | Yes | A vehicle id from GET /v1/vehicles (preset or veh_…). |\n| `name` | string | No | The saved vehicle's name. Read-only. |\n| `type` | string | No | The saved vehicle's type. Read-only. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `length` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `width` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `height` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `max_weight` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `kg`. |\n| `quantity` | integer | No | How many identical vehicles of this definition are available. Default: `1`. 1–100. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). Default: `false`. |\n| `aux_deck_height` | number | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. At most 32 entries. |\n| `tractor_weight` | number | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `empty_trailer_weight` | number | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `front_axle_to_middle_axle` | number | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_tractor_cog` | number | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_kingpin` | number | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. Requires access (`axle_loads`). |\n| `kingpin_to_rear_axle` | number | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_cog_to_rear_axle` | number | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_start_to_kingpin` | number | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. Requires access (`axle_loads`). |\n| `max_middle_axle_weight` | number | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. Requires access (`axle_loads`). |\n| `max_rear_axle_weight` | number | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. Requires access (`axle_loads`). |\n\n### Rect\n\nA rectangle on the vehicle floor (min-corner origin).\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n### Item\n\nAn item definition (one SKU / package type). `quantity` identical units are loaded; each unit is reported as one placement with an `instance` number. Name a saved template with `template_id` and send only what differs.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Your identifier, unique within the request. Results refer to it as item_id. Defaults to template_id. Max 128 characters. |\n| `template_id` | string | No | A template id (tpl_…) from GET /v1/templates. Supplies every item field; fields sent next to it override them. |\n| `label` | string | No | Display label (defaults to the id). Max 255 characters. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. Default: `\"box\"`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | Yes | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | Yes | Weight of ONE unit. Unit: `kg`. |\n| `quantity` | integer | No | Number of identical units. Default: `1`. 1–10000. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. Default: `true`. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Default: `0`. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. Default: `\"all\"`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. Default: `false`. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. Default: `0`. |\n| `group` | string | No | Name of the group this item belongs to (see `groups` and `options.group_routing`). |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Default: `0`. Unit: `cm`. |\n\n### Group\n\nA group of items (a customer, a stop, an order). With group routing, group order decides the loading sequence: order 1 is loaded first (deepest, at the front wall), the highest order last (at the door). Items without a group go last.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | Yes | Group name, referenced by Item.group. Max 128 characters. |\n| `order` | integer | No | Loading order (lower = loaded first). Defaults to the position in the list. |\n| `color` | string | No | Display colour as #rrggbb. |\n\n### ProjectOptions\n\nThe pack options a project stores.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `packing_mode` | string | No | `auto` = when the listed vehicles are full, more copies of the most suitable vehicle are added (up to the job's vehicle limit); `fleet_only` = only the listed vehicles are used, anything that does not fit is reported as unplaced. One of: `auto`, `fleet_only`. Default: `\"auto\"`. |\n| `group_routing` | string | No | `none` = groups are informational; `wall` = each group gets its own section of the vehicle; `routing` = groups load in order and a later group may sit on or beside an earlier one but never below it (multi-stop delivery); `no_stack` = groups load in order side by side, never on top of each other. One of: `none`, `wall`, `routing`, `no_stack`. Default: `\"none\"`. |\n| `allow_overweight` | boolean | No | Ignore vehicle payload limits. Default: `false`. |\n\n## Example request\n\n```bash\ncurl -X PATCH \"https://runner.cargoduo.com/v1/projects/prj_3051\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"vehicles\": [\n      {\n        \"vehicle_id\": \"veh_118\",\n        \"quantity\": 2\n      }\n    ]\n  }'\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/projects/prj_3051\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"vehicles\": [\n      {\n        \"vehicle_id\": \"veh_118\",\n        \"quantity\": 2\n      }\n    ]\n  }),\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.patch(\n    \"https://runner.cargoduo.com/v1/projects/prj_3051\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    json={\n        \"vehicles\": [\n            {\n                \"vehicle_id\": \"veh_118\",\n                \"quantity\": 2,\n            },\n        ],\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"id\": \"prj_3051\",\n  \"object\": \"project\",\n  \"name\": \"Order 1042\",\n  \"status\": \"draft\",\n  \"item_count\": 58,\n  \"vehicle_count\": 2,\n  \"app_url\": \"https://app.cargoduo.com/projects/3051\",\n  \"created_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"updated_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"created_at\": \"2026-10-19T09:10:03Z\",\n  \"updated_at\": \"2026-10-19T09:22:03Z\",\n  \"options\": {\n    \"packing_mode\": \"auto\",\n    \"group_routing\": \"none\",\n    \"allow_overweight\": false\n  },\n  \"vehicles\": [\n    {\n      \"id\": \"pv_5121\",\n      \"vehicle_id\": \"veh_118\",\n      \"name\": \"Mega trailer 13.6 m\",\n      \"type\": \"trailer\",\n      \"quantity\": 2,\n      \"length\": 1360,\n      \"width\": 248,\n      \"height\": 300,\n      \"max_weight\": 24000\n    }\n  ],\n  \"items\": [\n    {\n      \"id\": \"item_88410\",\n      \"label\": \"Euro pallet\",\n      \"shape\": \"box\",\n      \"length\": 120,\n      \"width\": 80,\n      \"height\": 140,\n      \"weight\": 450,\n      \"quantity\": 18,\n      \"stackable\": false,\n      \"rotation\": \"all\",\n      \"color\": \"#3b82f6\"\n    },\n    {\n      \"id\": \"item_88411\",\n      \"label\": \"Carton A\",\n      \"shape\": \"box\",\n      \"length\": 60,\n      \"width\": 40,\n      \"height\": 40,\n      \"weight\": 12,\n      \"quantity\": 40,\n      \"stackable\": true,\n      \"rotation\": \"upright\",\n      \"color\": \"#f59e0b\"\n    }\n  ],\n  \"groups\": [],\n  \"plan\": null\n}\n```\n\n## Responses\n\n### 200 OK\n\nThe project after the change. Returns `Project`.\n\n### 400 Bad request\n\n| Code | Meaning |\n|---|---|\n| `invalid_json` | The body is not valid JSON or a field has the wrong type. |\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `plan_limit_reached` | The organization's plan allows no more projects or cargo items. |\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `project_not_found` | No project with this id in your organization. |\n\n### 413 Payload too large\n\n| Code | Meaning |\n|---|---|\n| `payload_too_large` | The request body exceeds 10 MB. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Project object\n\nA load plan of your organization: the same project the app opens in its 3D editor.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Project id (prj_…). |\n| `object` | string | No | One of: `project`. |\n| `name` | string | No |  |\n| `status` | string | No | `draft` = never packed or inputs replaced; `calculated` = has a current plan; `stale` = a setting changed since the plan was made. One of: `draft`, `calculated`, `stale`, `error`. |\n| `item_count` | integer | No | Item units (sum of quantities). |\n| `vehicle_count` | integer | No | Vehicles (sum of quantities). |\n| `app_url` | string | No | Opens the project in the app's 3D editor. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string | No | RFC 3339. |\n| `updated_at` | string | No | RFC 3339. |\n| `options` | ProjectOptions | No |  |\n| `vehicles` | ProjectVehicle[] | No |  |\n| `items` | Item[] | No | Cargo in the /v1/pack Item shape; ids are item_…. |\n| `groups` | Group[] | No |  |\n| `plan` | ProjectPlan \\| null | No | The current plan; `null` when the project has none. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n### ProjectOptions\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `packing_mode` | string | No | `auto` = when the listed vehicles are full, more copies of the most suitable vehicle are added (up to the job's vehicle limit); `fleet_only` = only the listed vehicles are used, anything that does not fit is reported as unplaced. One of: `auto`, `fleet_only`. Default: `\"auto\"`. |\n| `group_routing` | string | No | `none` = groups are informational; `wall` = each group gets its own section of the vehicle; `routing` = groups load in order and a later group may sit on or beside an earlier one but never below it (multi-stop delivery); `no_stack` = groups load in order side by side, never on top of each other. One of: `none`, `wall`, `routing`, `no_stack`. Default: `\"none\"`. |\n| `allow_overweight` | boolean | No | Ignore vehicle payload limits. Default: `false`. |\n\n### ProjectVehicle\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Slot id (pv_…). Results of the project's jobs refer to it as vehicle_id. Read-only. |\n| `vehicle_id` | string | Yes | A vehicle id from GET /v1/vehicles (preset or veh_…). |\n| `name` | string | No | The saved vehicle's name. Read-only. |\n| `type` | string | No | The saved vehicle's type. Read-only. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `length` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `width` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `height` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `cm`. |\n| `max_weight` | number | No | The size the slot packs with; sent, an override of the saved vehicle's. Unit: `kg`. |\n| `quantity` | integer | No | How many identical vehicles of this definition are available. Default: `1`. 1–100. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). Default: `false`. |\n| `aux_deck_height` | number | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. At most 32 entries. |\n| `tractor_weight` | number | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `empty_trailer_weight` | number | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `front_axle_to_middle_axle` | number | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_tractor_cog` | number | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_kingpin` | number | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. Requires access (`axle_loads`). |\n| `kingpin_to_rear_axle` | number | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_cog_to_rear_axle` | number | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_start_to_kingpin` | number | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. Requires access (`axle_loads`). |\n| `max_middle_axle_weight` | number | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. Requires access (`axle_loads`). |\n| `max_rear_axle_weight` | number | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. Requires access (`axle_loads`). |\n\n### Rect\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n### Item\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Your identifier, unique within the request. Results refer to it as item_id. Defaults to template_id. Max 128 characters. |\n| `template_id` | string | No | A template id (tpl_…) from GET /v1/templates. Supplies every item field; fields sent next to it override them. |\n| `label` | string | No | Display label (defaults to the id). Max 255 characters. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. Default: `\"box\"`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | Yes | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | Yes | Weight of ONE unit. Unit: `kg`. |\n| `quantity` | integer | No | Number of identical units. Default: `1`. 1–10000. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. Default: `true`. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Default: `0`. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. Default: `\"all\"`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. Default: `false`. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. Default: `0`. |\n| `group` | string | No | Name of the group this item belongs to (see `groups` and `options.group_routing`). |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Default: `0`. Unit: `cm`. |\n\n### Group\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | Yes | Group name, referenced by Item.group. Max 128 characters. |\n| `order` | integer | No | Loading order (lower = loaded first). Defaults to the position in the list. |\n| `color` | string | No | Display colour as #rrggbb. |\n\n### ProjectPlan\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `items_placed` | integer | No | Units placed. |\n| `items_unplaced` | integer | No | Units that could not be placed. |\n| `vehicles_used` | integer | No | Vehicles carrying at least one item. |\n| `total_weight` | number | No | Weight loaded. Unit: `kg`. |\n| `volume_utilization` | number | No | Average volume utilization of the used vehicles, 0–1. |\n| `job_id` | string | No | The API job whose plan this is, while nothing changed it since. Empty for a plan made or edited in the app. |\n| `pdf_url` | string | No | That job's PDF link (until its expires_at). Empty otherwise. |\n\n\n## Related\n\n- [Get a project](https://runner.cargoduo.com/api/public/api-docs/pages/get-project.md): A project with its vehicles, items, groups, options and a summary of its current plan (with the PDF link while the plan is the one an API job saved).\n- [Pack a project](https://runner.cargoduo.com/api/public/api-docs/pages/pack-project.md): Packs the project's vehicles and items exactly like POST /v1/pack: same price, credit reservation, limits, Idempotency-Key, ?wait= and job.\n"},{"slug":"delete-project","title":"Delete a project","section":"API reference","group":"Projects","summary":"Deletes a project with its vehicles, items and plan history, in the app too.","keywords":"delete /v1/projects/{id} deleteproject","method":"DELETE","path":"/v1/projects/{id}","operation_id":"deleteProject","related":["list-projects"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/delete-project.md","markdown":"# Delete a project\n\nDeletes a project with its vehicles, items and plan history, in the app too.\n\n```http\nDELETE https://runner.cargoduo.com/v1/projects/{id}\n```\n\nFree: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Project id (prj_…). |\n\n## Example request\n\n```bash\ncurl -X DELETE \"https://runner.cargoduo.com/v1/projects/prj_3051\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/projects/prj_3051\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.delete(\n    \"https://runner.cargoduo.com/v1/projects/prj_3051\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"id\": \"prj_3051\",\n  \"object\": \"project\",\n  \"deleted\": true\n}\n```\n\n## Responses\n\n### 200 OK\n\nDeleted. Returns `Deleted`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `project_not_found` | No project with this id in your organization. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Deleted object\n\nAnswer to a DELETE.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No |  |\n| `object` | string | No | One of: `vehicle`, `template`, `project`. |\n| `deleted` | boolean | No | Always `true`. |\n\n\n## Related\n\n- [List projects](https://runner.cargoduo.com/api/public/api-docs/pages/list-projects.md): Your organization's projects (the app's load plans), newest first, one page at a time.\n"},{"slug":"pack-project","title":"Pack a project","section":"API reference","group":"Projects","summary":"Packs the project's vehicles and items exactly like POST /v1/pack: same price, credit reservation, limits, Idempotency-Key, ?wait= and job.","keywords":"post /v1/projects/{id}/pack packproject","method":"POST","path":"/v1/projects/{id}/pack","operation_id":"packProject","related":["create-pack-job","get-project","credits"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/pack-project.md","markdown":"# Pack a project\n\nPacks the project's vehicles and items exactly like POST /v1/pack: same price, credit reservation, limits, Idempotency-Key, ?wait= and job.\n\n```http\nPOST https://runner.cargoduo.com/v1/projects/{id}/pack\n```\n\nWhen the job succeeds its plan is saved into the project, which then opens in the app with that plan. If the project changed while the job ran, the plan stays on the job only (warning project_not_saved). Result ids are the project's: vehicle_id is the slot id (pv_…), item_id the item id (item_…).\n\n## Authentication\n\nBearer API key with the `pack:write` and `resources:write` scopes. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Project id (prj_…). |\n\n## Query parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `wait` | integer | No | Seconds to hold the response for the job to finish. Finished in time: 200 with the result inlined. Otherwise: 202. Default: `0`. 0–60. |\n\n## Header parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `Idempotency-Key` | string | No | Any unique string, such as a UUID or your order id. The same key within 24 h returns the original job instead of creating and charging a new one. Max 128 characters. |\n\n## Body parameters\n\nOptional body of POST /v1/projects/{id}/pack. The vehicles, items and groups come from the project.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `reference` | string | No | Your reference, echoed on the job. Defaults to the project name. Max 128 characters. |\n| `options` | ProjectPackOptions | No | Run options. Every field is optional. |\n\n### ProjectPackOptions\n\nRun options. `packing_mode` and `allow_overweight` default to the project's and are saved to it with the plan; `group_routing` is set on the project (PATCH).\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `packing_mode` | string | No | `auto` = when the listed vehicles are full, more copies of the most suitable vehicle are added (up to the job's vehicle limit); `fleet_only` = only the listed vehicles are used, anything that does not fit is reported as unplaced. One of: `auto`, `fleet_only`. |\n| `allow_overweight` | boolean | No | Ignore vehicle payload limits. |\n| `max_stack_items` | integer | No | Maximum items in one stack. 0 = unlimited. Default: `0`. |\n| `max_stack_density_kg_m2` | number | No | Maximum stack weight per floor area. 0 = unlimited. Default: `0`. Unit: `kg/m²`. |\n| `lock_rotations` | boolean | No | Keep the first orientation that fits the vehicle height. Default: `false`. |\n| `group_identical_bins` | boolean | No | Treat identical vehicles as one pool. Default: `false`. |\n| `time_limit` | number | No | Upper bound on compute time for the whole job. 0 = automatic (capped at max_time_limit_seconds from GET /v1/limits). When the limit is reached the best plan found so far is returned. You pay only for the compute time actually used. Default: `0`. Unit: `s`. |\n| `enforce_axle` | boolean | No | Reject placements that overload an axle group, on vehicles that carry all ten axle-model fields. Default: `false`. Requires access (`axle_loads`). |\n\n## Example request\n\n```bash\ncurl -X POST \"https://runner.cargoduo.com/v1/projects/prj_3051/pack\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\" \\\n  -H \"Idempotency-Key: order-1042-v1\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"reference\": \"order-1042\"\n  }'\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/projects/prj_3051/pack\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n    \"Idempotency-Key\": \"order-1042-v1\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"reference\": \"order-1042\"\n  }),\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.post(\n    \"https://runner.cargoduo.com/v1/projects/prj_3051/pack\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\", \"Idempotency-Key\": \"order-1042-v1\"},\n    json={\n        \"reference\": \"order-1042\",\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`202` Accepted\n\n```json\n{\n  \"id\": \"job_4bK9mT2xQe7LpW1zRc8VdN3f\",\n  \"object\": \"job\",\n  \"status\": \"queued\",\n  \"reference\": \"order-1042\",\n  \"source\": \"api\",\n  \"project_id\": \"prj_3051\",\n  \"created_at\": \"2026-10-19T09:12:03Z\",\n  \"started_at\": null,\n  \"finished_at\": null,\n  \"progress\": {\n    \"stage\": \"queued\",\n    \"vehicles_done\": 0,\n    \"placed_items\": 0\n  },\n  \"summary\": {\n    \"items_requested\": 58,\n    \"items_placed\": 0,\n    \"items_unplaced\": 0,\n    \"vehicles_used\": 0,\n    \"total_weight\": 0,\n    \"volume_utilization\": 0,\n    \"queue_ms\": 0,\n    \"compute_ms\": 0\n  },\n  \"cost\": {\n    \"credits\": 0,\n    \"reserved_credits\": 1.11,\n    \"rate_card_version\": 1,\n    \"breakdown\": {\n      \"base\": 0,\n      \"vehicles\": 0,\n      \"compute\": 0,\n      \"large_load\": 0\n    }\n  },\n  \"error\": null,\n  \"warnings\": [],\n  \"result_url\": \"https://runner.cargoduo.com/v1/jobs/job_4bK9mT2xQe7LpW1zRc8VdN3f/result\",\n  \"pdf_url\": \"\",\n  \"expires_at\": \"2026-11-02T09:12:03Z\"\n}\n```\n\n## Responses\n\n### 202 Accepted\n\nJob accepted (queued or running). Returns `Job`.\n\n### 200 OK\n\nJob finished within ?wait, result inlined. Also an idempotent replay of a finished job. Returns `Job`.\n\n### 400 Bad request\n\n| Code | Meaning |\n|---|---|\n| `invalid_json` | The body is not valid JSON or a field has the wrong type. |\n| `idempotency_key_invalid` | The Idempotency-Key header is longer than 128 characters. |\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 402 Payment required\n\n| Code | Meaning |\n|---|---|\n| `insufficient_credits` | Available credits do not cover the smallest possible run of this job. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `project_not_found` | No project with this id in your organization. |\n\n### 409 Conflict\n\n| Code | Meaning |\n|---|---|\n| `project_busy` | The project is being packed right now (in the app or by another job). |\n\n### 413 Payload too large\n\n| Code | Meaning |\n|---|---|\n| `payload_too_large` | The request body exceeds 10 MB. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `too_many_jobs` | The organization already has the maximum number of queued + running jobs. |\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Job object\n\nA packing job. Created by POST /v1/pack; poll GET /v1/jobs/{id} until `status` is `succeeded`, `failed` or `canceled`.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Job id (job_…). |\n| `object` | string | Yes | Always `job`. One of: `job`. |\n| `status` | string | Yes | `queued` → `running` → `succeeded` \\| `failed` \\| `canceled`. One of: `queued`, `running`, `succeeded`, `failed`, `canceled`. |\n| `reference` | string | No | Your reference from the request. |\n| `source` | string | No | Where the job was created. One of: `api`, `playground`. |\n| `project_id` | string | No | The project (prj_…) a POST /v1/projects/{id}/pack job saves its plan into. Absent on other jobs. |\n| `created_at` | string | Yes | RFC 3339 timestamp. |\n| `started_at` | string \\| null | No | When computing started (RFC 3339) or `null`. |\n| `finished_at` | string \\| null | No | When the job ended (RFC 3339) or `null`. |\n| `progress` | JobProgress | No | Live progress while queued/running. |\n| `summary` | JobSummary | No | Counts and timings. |\n| `cost` | JobCost | No | Credits charged (final once the job ended) and the reservation held while it ran. |\n| `error` | JobError \\| null | No | Why the job failed or was canceled; `null` otherwise. |\n| `warnings` | Warning[] | No | Non-blocking remarks about the request (also repeated in the result). |\n| `result_url` | string | No | GET this URL for the full result once `status` is `succeeded`. |\n| `pdf_url` | string | No | A browser link that renders the load plan as a PDF (3D views + loading steps). Valid until expires_at. Empty after expiry. |\n| `expires_at` | string | No | When the request/result bodies and the PDF link are deleted (RFC 3339). Job metadata and charges are kept. |\n| `result` | Result | No | The full result, inlined only when requested (?include=result) or when POST /v1/pack?wait= finished in time. |\n\n### JobProgress\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `stage` | string | No | Current stage, such as `queued`, `running`, `finalizing` or `done`. Informational: branch on `Job.status`. |\n| `vehicles_done` | integer | No | Vehicles packed so far. |\n| `placed_items` | integer | No | Item units placed so far. |\n\n### JobSummary\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `items_requested` | integer | No | Total item units (sum of quantities). |\n| `items_placed` | integer | No | Units placed. |\n| `items_unplaced` | integer | No | Units that could not be placed. |\n| `vehicles_used` | integer | No | Vehicles that carry at least one item. |\n| `total_weight` | number | No | Weight loaded. Unit: `kg`. |\n| `volume_utilization` | number | No | Average volume utilization of the used vehicles, 0–1. |\n| `queue_ms` | integer | No | Time spent waiting for a compute slot (not billed). |\n| `compute_ms` | integer | No | Compute time (billed). |\n\n### JobCost\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `credits` | number | No | Credits charged (0 until the job ends). Failed jobs are not charged, except time_limit_exceeded, which is charged like a canceled job. Unit: `credits`. |\n| `reserved_credits` | number | No | Credits held while the job was queued/running (the worst-case price). Released when it ends. Unit: `credits`. |\n| `rate_card_version` | integer | No | Rate card the job is priced with. |\n| `breakdown` | CostBreakdown | No | Itemized price. |\n\n### CostBreakdown\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `base` | number | No | Per-job fee. Unit: `credits`. |\n| `vehicles` | number | No | Per loaded vehicle (not charged for canceled or time_limit_exceeded jobs). Unit: `credits`. |\n| `compute` | number | No | Per compute-second, pro-rated per millisecond. Unit: `credits`. |\n| `large_load` | number | No | Surcharge per started 100 items above the large-load threshold. Unit: `credits`. |\n\n### JobError\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code (see Errors). |\n| `message` | string | No | English explanation. |\n\n### Warning\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `code` | string | No | Machine code. |\n| `message` | string | No | English explanation. |\n\n### Result\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `job_id` | string | No | The job. |\n| `vehicles` | ResultVehicle[] | No | Loaded vehicles in loading order (empty vehicles are left out). |\n| `unplaced` | UnplacedItem[] | No | Units that could not be placed, with the reason. |\n| `warnings` | Warning[] | No | Remarks about the plan. |\n| `pdf_url` | string | No | Browser link that renders the plan as a PDF. |\n| `expires_at` | string | No | When this result is deleted (RFC 3339). |\n\n### ResultVehicle\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `index` | integer | No | 0-based position in the plan. |\n| `vehicle_id` | string | No | Your vehicle definition id (for a project job: the slot id, pv_…). |\n| `copy` | integer | No | 1-based copy number of that definition (quantity \u003e 1, or added in auto mode). |\n| `auto_added` | boolean | No | Added by `packing_mode` `auto` beyond the `quantity` you listed. |\n| `name` | string | No | Vehicle name. |\n| `type` | string | No | Vehicle type. |\n| `length` | number | No | Cargo-space length. Unit: `cm`. |\n| `width` | number | No | Cargo-space width. Unit: `cm`. |\n| `height` | number | No | Cargo-space height. Unit: `cm`. |\n| `max_weight` | number | No | Payload limit. Unit: `kg`. |\n| `items_placed` | integer | No | Units in this vehicle. |\n| `total_weight` | number | No | Weight in this vehicle. Unit: `kg`. |\n| `volume_utilization` | number | No | Used volume / cargo-space volume, 0–1. |\n| `weight_utilization` | number | No | total_weight / max_weight, 0–1 (may exceed 1 with allow_overweight). |\n| `center_of_gravity` | Point | No | Centre of gravity of the load (cm). |\n| `placements` | Placement[] | No | Every unit in this vehicle. |\n| `axle_loads` | AxleLoad[] | No | Load per axle (vehicles with axle data only). Requires access (`axle_loads`). |\n\n### Point\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | No | Unit: `cm`. |\n| `y` | number | No | Unit: `cm`. |\n| `z` | number | No | Unit: `cm`. |\n\n### Placement\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id (for a project job: the item id, item_…). |\n| `instance` | integer | No | 1-based unit number within that item definition (unique across the whole plan). |\n| `label` | string | No | Item label. |\n| `x` | number | No | Min-corner position along the length. Unit: `cm`. |\n| `y` | number | No | Min-corner height above the floor. Unit: `cm`. |\n| `z` | number | No | Min-corner position across the width. Unit: `cm`. |\n| `length` | number | No | Size along x as placed. Unit: `cm`. |\n| `width` | number | No | Size along z as placed. Unit: `cm`. |\n| `height` | number | No | Size along y as placed. Unit: `cm`. |\n| `rotated` | boolean | No | Placed in a different orientation than defined. |\n| `orientation` | string | No | Which defined side lies along x, z and y: `lwh` = as defined, `wlh` = turned 90° around the vertical axis, `lhw`, `hwl`, `whl`, `hlw` = tipped onto a side. |\n| `step` | integer | No | 1-based loading step within the vehicle (load in ascending order). |\n| `weight` | number | No | Unit weight. Unit: `kg`. |\n\n### AxleLoad\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `name` | string | No | `Axle 1`, `Axle 2`… in the order of `axle_positions`. |\n| `position` | number | No | Axle position from the front of the cargo area. Unit: `cm`. |\n| `load` | number | No | Cargo load on the axle. Unit: `kg`. |\n| `max` | number | No | Configured maximum (0 = none). Unit: `kg`. |\n| `ok` | boolean | No | `load` ≤ `max` (always `true` without a maximum). |\n\n### UnplacedItem\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `item_id` | string | No | Your item definition id. |\n| `instance` | integer | No | 1-based unit number. |\n| `label` | string | No | Item label. |\n| `reason` | string | No | Why it could not be placed. |\n\n\n## Related\n\n- [Create a packing job](https://runner.cargoduo.com/api/public/api-docs/pages/create-pack-job.md): Validates the request, reserves the worst-case price from your credits and queues a job.\n- [Get a project](https://runner.cargoduo.com/api/public/api-docs/pages/get-project.md): A project with its vehicles, items, groups, options and a summary of its current plan (with the PDF link while the plan is the one an API job saved).\n- [Credits \u0026 pricing](https://runner.cargoduo.com/api/public/api-docs/pages/credits.md): What a job costs and where credits come from.\n"},{"slug":"list-vehicles","title":"List vehicles","section":"API reference","group":"Vehicles","summary":"Every vehicle you can name with vehicle_id: the presets (containers, trailers, trucks, pallets of the types your organization may use), then your organization's own vehicles.","keywords":"get /v1/vehicles listvehicles","method":"GET","path":"/v1/vehicles","operation_id":"listVehicles","related":["create-pack-job","create-vehicle","saved-data"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/list-vehicles.md","markdown":"# List vehicles\n\nEvery vehicle you can name with vehicle_id: the presets (containers, trailers, trucks, pallets of the types your organization may use), then your organization's own vehicles.\n\n```http\nGET https://runner.cargoduo.com/v1/vehicles\n```\n\nFree: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Query parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `limit` | integer | No | Page size. Default: `20`. 1–100. |\n| `cursor` | string | No | next_cursor of the previous page. |\n| `kind` | string | No | Only presets (`preset`), or only your own vehicles (`custom`). One of: `preset`, `custom`. |\n| `type` | string | No | Only vehicles of this type. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `q` | string | No | Only vehicles whose name contains this text. |\n| `created_by` | string | No | Only what this member created: `me` (the key's creator) or a member id (`usr_…`). |\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/vehicles?type=trailer\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/vehicles?type=trailer\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/vehicles\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    params={\n        \"type\": \"trailer\",\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"standard-trailer-13-6m\",\n      \"object\": \"vehicle\",\n      \"kind\": \"preset\",\n      \"name\": \"Standard Trailer (13.6m)\",\n      \"type\": \"trailer\",\n      \"length\": 1362,\n      \"width\": 248,\n      \"height\": 270,\n      \"max_weight\": 24000,\n      \"floor_load_capacity\": 1800,\n      \"axle_positions\": [\n        30,\n        938,\n        1069,\n        1200\n      ],\n      \"axle_max_loads\": [\n        10000,\n        8000,\n        8000,\n        8000\n      ],\n      \"cog_range_x\": 50,\n      \"cog_range_y\": 20,\n      \"aux_deck\": false,\n      \"aux_deck_height\": null,\n      \"aux_deck_max_weight\": null,\n      \"tractor_weight\": null,\n      \"empty_trailer_weight\": null,\n      \"front_axle_to_middle_axle\": null,\n      \"front_axle_to_tractor_cog\": null,\n      \"front_axle_to_kingpin\": null,\n      \"kingpin_to_rear_axle\": null,\n      \"trailer_cog_to_rear_axle\": null,\n      \"trailer_start_to_kingpin\": null,\n      \"max_middle_axle_weight\": null,\n      \"max_rear_axle_weight\": null,\n      \"blocked_areas\": [],\n      \"created_by\": null,\n      \"updated_by\": null,\n      \"created_at\": null\n    },\n    {\n      \"id\": \"veh_118\",\n      \"object\": \"vehicle\",\n      \"kind\": \"custom\",\n      \"name\": \"Mega trailer 13.6 m\",\n      \"type\": \"trailer\",\n      \"length\": 1360,\n      \"width\": 248,\n      \"height\": 300,\n      \"max_weight\": 24000,\n      \"floor_load_capacity\": null,\n      \"axle_positions\": [],\n      \"axle_max_loads\": [],\n      \"cog_range_x\": null,\n      \"cog_range_y\": null,\n      \"aux_deck\": false,\n      \"aux_deck_height\": null,\n      \"aux_deck_max_weight\": null,\n      \"tractor_weight\": null,\n      \"empty_trailer_weight\": null,\n      \"front_axle_to_middle_axle\": null,\n      \"front_axle_to_tractor_cog\": null,\n      \"front_axle_to_kingpin\": null,\n      \"kingpin_to_rear_axle\": null,\n      \"trailer_cog_to_rear_axle\": null,\n      \"trailer_start_to_kingpin\": null,\n      \"max_middle_axle_weight\": null,\n      \"max_rear_axle_weight\": null,\n      \"blocked_areas\": [],\n      \"created_by\": {\n        \"id\": \"usr_7\",\n        \"name\": \"Elif Kaya\"\n      },\n      \"updated_by\": {\n        \"id\": \"usr_7\",\n        \"name\": \"Elif Kaya\"\n      },\n      \"created_at\": \"2026-10-02T08:41:17Z\"\n    }\n  ],\n  \"next_cursor\": \"\"\n}\n```\n\nAbridged: one preset and one of your vehicles are shown.\n\n## Responses\n\n### 200 OK\n\nOne page of vehicles. Returns `SavedVehicleList`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The SavedVehicleList object\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `data` | SavedVehicle[] | No | Presets first, then your own vehicles, oldest first. |\n| `next_cursor` | string | No | Pass as ?cursor= for the next page; empty on the last page. |\n\n### SavedVehicle\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Preset id, or veh_… for your own vehicles. |\n| `object` | string | No | One of: `vehicle`. |\n| `kind` | string | No | `preset` = the predefined catalogue; `custom` = your organization's. One of: `preset`, `custom`. |\n| `type` | string | No | Vehicle type. Only the types listed in GET /v1/limits (allowed_vehicle_types) are accepted. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `name` | string | No | Display name. |\n| `length` | number | No | Internal cargo-space length, along x (from the front wall toward the door). Unit: `cm`. |\n| `width` | number | No | Internal cargo-space width, along z. Unit: `cm`. |\n| `height` | number | No | Internal cargo-space height, along y. Unit: `cm`. |\n| `max_weight` | number | No | Maximum payload. Unit: `kg`. |\n| `floor_load_capacity` | number \\| null | No | Floor load capacity: 0 or more (0 = no limit). Unit: `kg/m²`. |\n| `axle_positions` | number[] | No | Axle (support point) positions measured from the front of the cargo area; negative = in front of it. Same length as axle_max_loads. Each between −300 (an axle ahead of the cargo area, e.g. a rigid truck's front axle under the cab) and the vehicle length. Unit: `cm`. |\n| `axle_max_loads` | number[] | No | Maximum cargo load per axle, in the order of axle_positions. Each greater than 0 and at most 100000. Unit: `kg`. |\n| `cog_range_x` | number \\| null | No | Allowed centre-of-gravity deviation from the middle of the length: 0 to length / 2. Unit: `cm`. |\n| `cog_range_y` | number \\| null | No | Allowed centre-of-gravity deviation from the middle of the width: 0 to width / 2. Unit: `cm`. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). |\n| `aux_deck_height` | number \\| null | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number \\| null | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `tractor_weight` | number \\| null | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. |\n| `empty_trailer_weight` | number \\| null | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. |\n| `front_axle_to_middle_axle` | number \\| null | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. |\n| `front_axle_to_tractor_cog` | number \\| null | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. |\n| `front_axle_to_kingpin` | number \\| null | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. |\n| `kingpin_to_rear_axle` | number \\| null | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. |\n| `trailer_cog_to_rear_axle` | number \\| null | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. |\n| `trailer_start_to_kingpin` | number \\| null | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. |\n| `max_middle_axle_weight` | number \\| null | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. |\n| `max_rear_axle_weight` | number \\| null | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string \\| null | No | RFC 3339; `null` for presets. |\n\n### Rect\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n\n## Related\n\n- [Create a packing job](https://runner.cargoduo.com/api/public/api-docs/pages/create-pack-job.md): Validates the request, reserves the worst-case price from your credits and queues a job.\n- [Create a vehicle](https://runner.cargoduo.com/api/public/api-docs/pages/create-vehicle.md): Saves a vehicle to your organization's fleet (it shows in the app too).\n- [Saved data \u0026 projects](https://runner.cargoduo.com/api/public/api-docs/pages/saved-data.md): Vehicles, templates and projects shared with the app.\n"},{"slug":"get-vehicle","title":"Get a vehicle","section":"API reference","group":"Vehicles","summary":"A preset or one of your vehicles.","keywords":"get /v1/vehicles/{id} getvehicle","method":"GET","path":"/v1/vehicles/{id}","operation_id":"getVehicle","related":["list-vehicles","update-vehicle"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/get-vehicle.md","markdown":"# Get a vehicle\n\nA preset or one of your vehicles.\n\n```http\nGET https://runner.cargoduo.com/v1/vehicles/{id}\n```\n\nFree: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | A preset id or one of your vehicles (veh_…). |\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/vehicles/veh_118\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/vehicles/veh_118\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/vehicles/veh_118\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"id\": \"veh_118\",\n  \"object\": \"vehicle\",\n  \"kind\": \"custom\",\n  \"name\": \"Mega trailer 13.6 m\",\n  \"type\": \"trailer\",\n  \"length\": 1360,\n  \"width\": 248,\n  \"height\": 300,\n  \"max_weight\": 24000,\n  \"floor_load_capacity\": null,\n  \"axle_positions\": [],\n  \"axle_max_loads\": [],\n  \"cog_range_x\": null,\n  \"cog_range_y\": null,\n  \"aux_deck\": false,\n  \"aux_deck_height\": null,\n  \"aux_deck_max_weight\": null,\n  \"tractor_weight\": null,\n  \"empty_trailer_weight\": null,\n  \"front_axle_to_middle_axle\": null,\n  \"front_axle_to_tractor_cog\": null,\n  \"front_axle_to_kingpin\": null,\n  \"kingpin_to_rear_axle\": null,\n  \"trailer_cog_to_rear_axle\": null,\n  \"trailer_start_to_kingpin\": null,\n  \"max_middle_axle_weight\": null,\n  \"max_rear_axle_weight\": null,\n  \"blocked_areas\": [],\n  \"created_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"updated_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"created_at\": \"2026-10-02T08:41:17Z\"\n}\n```\n\n## Responses\n\n### 200 OK\n\nThe vehicle. Returns `SavedVehicle`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `vehicle_not_found` | No vehicle with this id among the presets or your organization's vehicles. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The SavedVehicle object\n\nA vehicle your organization can name with `vehicle_id`: a preset (`kind` `preset`, read-only) or one of your own (`kind` `custom`, `veh_…`). Your own vehicles are the fleet the app shows.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Preset id, or veh_… for your own vehicles. |\n| `object` | string | No | One of: `vehicle`. |\n| `kind` | string | No | `preset` = the predefined catalogue; `custom` = your organization's. One of: `preset`, `custom`. |\n| `type` | string | No | Vehicle type. Only the types listed in GET /v1/limits (allowed_vehicle_types) are accepted. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `name` | string | No | Display name. |\n| `length` | number | No | Internal cargo-space length, along x (from the front wall toward the door). Unit: `cm`. |\n| `width` | number | No | Internal cargo-space width, along z. Unit: `cm`. |\n| `height` | number | No | Internal cargo-space height, along y. Unit: `cm`. |\n| `max_weight` | number | No | Maximum payload. Unit: `kg`. |\n| `floor_load_capacity` | number \\| null | No | Floor load capacity: 0 or more (0 = no limit). Unit: `kg/m²`. |\n| `axle_positions` | number[] | No | Axle (support point) positions measured from the front of the cargo area; negative = in front of it. Same length as axle_max_loads. Each between −300 (an axle ahead of the cargo area, e.g. a rigid truck's front axle under the cab) and the vehicle length. Unit: `cm`. |\n| `axle_max_loads` | number[] | No | Maximum cargo load per axle, in the order of axle_positions. Each greater than 0 and at most 100000. Unit: `kg`. |\n| `cog_range_x` | number \\| null | No | Allowed centre-of-gravity deviation from the middle of the length: 0 to length / 2. Unit: `cm`. |\n| `cog_range_y` | number \\| null | No | Allowed centre-of-gravity deviation from the middle of the width: 0 to width / 2. Unit: `cm`. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). |\n| `aux_deck_height` | number \\| null | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number \\| null | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `tractor_weight` | number \\| null | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. |\n| `empty_trailer_weight` | number \\| null | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. |\n| `front_axle_to_middle_axle` | number \\| null | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. |\n| `front_axle_to_tractor_cog` | number \\| null | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. |\n| `front_axle_to_kingpin` | number \\| null | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. |\n| `kingpin_to_rear_axle` | number \\| null | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. |\n| `trailer_cog_to_rear_axle` | number \\| null | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. |\n| `trailer_start_to_kingpin` | number \\| null | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. |\n| `max_middle_axle_weight` | number \\| null | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. |\n| `max_rear_axle_weight` | number \\| null | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string \\| null | No | RFC 3339; `null` for presets. |\n\n### Rect\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n\n## Related\n\n- [List vehicles](https://runner.cargoduo.com/api/public/api-docs/pages/list-vehicles.md): Every vehicle you can name with vehicle_id: the presets (containers, trailers, trucks, pallets of the types your organization may use), then your organization's own vehicles.\n- [Update a vehicle](https://runner.cargoduo.com/api/public/api-docs/pages/update-vehicle.md): Changes one of your vehicles.\n"},{"slug":"create-vehicle","title":"Create a vehicle","section":"API reference","group":"Vehicles","summary":"Saves a vehicle to your organization's fleet (it shows in the app too).","keywords":"post /v1/vehicles createvehicle","method":"POST","path":"/v1/vehicles","operation_id":"createVehicle","related":["list-vehicles","create-pack-job"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/create-vehicle.md","markdown":"# Create a vehicle\n\nSaves a vehicle to your organization's fleet (it shows in the app too).\n\n```http\nPOST https://runner.cargoduo.com/v1/vehicles\n```\n\nSame fields and access rules as a /v1/pack vehicle, judged by the app's size and payload limits for its type. Free: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Body parameters\n\nA vehicle of your own: the /v1/pack `Vehicle` fields without the per-job ones (`id`, `vehicle_id`, `quantity`). Judged by the app's plausibility limits for its type.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `type` | string | Yes | Vehicle type. Only the types listed in GET /v1/limits (allowed_vehicle_types) are accepted. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `name` | string | Yes | Display name. Max 255 characters. |\n| `length` | number | Yes | Internal cargo-space length, along x (from the front wall toward the door). Unit: `cm`. |\n| `width` | number | Yes | Internal cargo-space width, along z. Unit: `cm`. |\n| `height` | number | Yes | Internal cargo-space height, along y. Unit: `cm`. |\n| `max_weight` | number | Yes | Maximum payload. Unit: `kg`. |\n| `floor_load_capacity` | number | No | Floor load capacity: 0 or more (0 = no limit). Unit: `kg/m²`. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). Default: `false`. |\n| `aux_deck_height` | number | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. At most 32 entries. |\n| `axle_positions` | number[] | No | Axle (support point) positions measured from the front of the cargo area; negative = in front of it. Same length as axle_max_loads. Each between −300 (an axle ahead of the cargo area, e.g. a rigid truck's front axle under the cab) and the vehicle length. At most 10 entries. Unit: `cm`. Requires access (`axle_loads`). |\n| `axle_max_loads` | number[] | No | Maximum cargo load per axle, in the order of axle_positions. Each greater than 0 and at most 100000. At most 10 entries. Unit: `kg`. Requires access (`axle_loads`). |\n| `cog_range_x` | number | No | Allowed centre-of-gravity deviation from the middle of the length: 0 to length / 2. Unit: `cm`. Requires access (`axle_loads`). |\n| `cog_range_y` | number | No | Allowed centre-of-gravity deviation from the middle of the width: 0 to width / 2. Unit: `cm`. Requires access (`axle_loads`). |\n| `tractor_weight` | number | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `empty_trailer_weight` | number | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `front_axle_to_middle_axle` | number | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_tractor_cog` | number | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_kingpin` | number | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. Requires access (`axle_loads`). |\n| `kingpin_to_rear_axle` | number | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_cog_to_rear_axle` | number | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_start_to_kingpin` | number | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. Requires access (`axle_loads`). |\n| `max_middle_axle_weight` | number | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. Requires access (`axle_loads`). |\n| `max_rear_axle_weight` | number | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. Requires access (`axle_loads`). |\n\n### Rect\n\nA rectangle on the vehicle floor (min-corner origin).\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n## Example request\n\n```bash\ncurl -X POST \"https://runner.cargoduo.com/v1/vehicles\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Mega trailer 13.6 m\",\n    \"type\": \"trailer\",\n    \"length\": 1360,\n    \"width\": 248,\n    \"height\": 300,\n    \"max_weight\": 24000\n  }'\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/vehicles\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"name\": \"Mega trailer 13.6 m\",\n    \"type\": \"trailer\",\n    \"length\": 1360,\n    \"width\": 248,\n    \"height\": 300,\n    \"max_weight\": 24000\n  }),\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.post(\n    \"https://runner.cargoduo.com/v1/vehicles\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    json={\n        \"name\": \"Mega trailer 13.6 m\",\n        \"type\": \"trailer\",\n        \"length\": 1360,\n        \"width\": 248,\n        \"height\": 300,\n        \"max_weight\": 24000,\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`201` \n\n```json\n{\n  \"id\": \"veh_118\",\n  \"object\": \"vehicle\",\n  \"kind\": \"custom\",\n  \"name\": \"Mega trailer 13.6 m\",\n  \"type\": \"trailer\",\n  \"length\": 1360,\n  \"width\": 248,\n  \"height\": 300,\n  \"max_weight\": 24000,\n  \"floor_load_capacity\": null,\n  \"axle_positions\": [],\n  \"axle_max_loads\": [],\n  \"cog_range_x\": null,\n  \"cog_range_y\": null,\n  \"aux_deck\": false,\n  \"aux_deck_height\": null,\n  \"aux_deck_max_weight\": null,\n  \"tractor_weight\": null,\n  \"empty_trailer_weight\": null,\n  \"front_axle_to_middle_axle\": null,\n  \"front_axle_to_tractor_cog\": null,\n  \"front_axle_to_kingpin\": null,\n  \"kingpin_to_rear_axle\": null,\n  \"trailer_cog_to_rear_axle\": null,\n  \"trailer_start_to_kingpin\": null,\n  \"max_middle_axle_weight\": null,\n  \"max_rear_axle_weight\": null,\n  \"blocked_areas\": [],\n  \"created_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"updated_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"created_at\": \"2026-10-02T08:41:17Z\"\n}\n```\n\n## Responses\n\n### 201 \n\nThe new vehicle. Returns `SavedVehicle`.\n\n### 400 Bad request\n\n| Code | Meaning |\n|---|---|\n| `invalid_json` | The body is not valid JSON or a field has the wrong type. |\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 413 Payload too large\n\n| Code | Meaning |\n|---|---|\n| `payload_too_large` | The request body exceeds 10 MB. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The SavedVehicle object\n\nA vehicle your organization can name with `vehicle_id`: a preset (`kind` `preset`, read-only) or one of your own (`kind` `custom`, `veh_…`). Your own vehicles are the fleet the app shows.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Preset id, or veh_… for your own vehicles. |\n| `object` | string | No | One of: `vehicle`. |\n| `kind` | string | No | `preset` = the predefined catalogue; `custom` = your organization's. One of: `preset`, `custom`. |\n| `type` | string | No | Vehicle type. Only the types listed in GET /v1/limits (allowed_vehicle_types) are accepted. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `name` | string | No | Display name. |\n| `length` | number | No | Internal cargo-space length, along x (from the front wall toward the door). Unit: `cm`. |\n| `width` | number | No | Internal cargo-space width, along z. Unit: `cm`. |\n| `height` | number | No | Internal cargo-space height, along y. Unit: `cm`. |\n| `max_weight` | number | No | Maximum payload. Unit: `kg`. |\n| `floor_load_capacity` | number \\| null | No | Floor load capacity: 0 or more (0 = no limit). Unit: `kg/m²`. |\n| `axle_positions` | number[] | No | Axle (support point) positions measured from the front of the cargo area; negative = in front of it. Same length as axle_max_loads. Each between −300 (an axle ahead of the cargo area, e.g. a rigid truck's front axle under the cab) and the vehicle length. Unit: `cm`. |\n| `axle_max_loads` | number[] | No | Maximum cargo load per axle, in the order of axle_positions. Each greater than 0 and at most 100000. Unit: `kg`. |\n| `cog_range_x` | number \\| null | No | Allowed centre-of-gravity deviation from the middle of the length: 0 to length / 2. Unit: `cm`. |\n| `cog_range_y` | number \\| null | No | Allowed centre-of-gravity deviation from the middle of the width: 0 to width / 2. Unit: `cm`. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). |\n| `aux_deck_height` | number \\| null | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number \\| null | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `tractor_weight` | number \\| null | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. |\n| `empty_trailer_weight` | number \\| null | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. |\n| `front_axle_to_middle_axle` | number \\| null | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. |\n| `front_axle_to_tractor_cog` | number \\| null | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. |\n| `front_axle_to_kingpin` | number \\| null | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. |\n| `kingpin_to_rear_axle` | number \\| null | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. |\n| `trailer_cog_to_rear_axle` | number \\| null | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. |\n| `trailer_start_to_kingpin` | number \\| null | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. |\n| `max_middle_axle_weight` | number \\| null | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. |\n| `max_rear_axle_weight` | number \\| null | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string \\| null | No | RFC 3339; `null` for presets. |\n\n### Rect\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n\n## Related\n\n- [List vehicles](https://runner.cargoduo.com/api/public/api-docs/pages/list-vehicles.md): Every vehicle you can name with vehicle_id: the presets (containers, trailers, trucks, pallets of the types your organization may use), then your organization's own vehicles.\n- [Create a packing job](https://runner.cargoduo.com/api/public/api-docs/pages/create-pack-job.md): Validates the request, reserves the worst-case price from your credits and queues a job.\n"},{"slug":"update-vehicle","title":"Update a vehicle","section":"API reference","group":"Vehicles","summary":"Changes one of your vehicles.","keywords":"patch /v1/vehicles/{id} updatevehicle","method":"PATCH","path":"/v1/vehicles/{id}","operation_id":"updateVehicle","related":["get-vehicle"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/update-vehicle.md","markdown":"# Update a vehicle\n\nChanges one of your vehicles.\n\n```http\nPATCH https://runner.cargoduo.com/v1/vehicles/{id}\n```\n\nPresets are read-only. Free: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | A preset id or one of your vehicles (veh_…). |\n\n## Body parameters\n\nThe fields to change. Absent fields keep their value; `null` clears an optional one.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `type` | string | No | Vehicle type. Only the types listed in GET /v1/limits (allowed_vehicle_types) are accepted. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `name` | string | No | Display name. Max 255 characters. |\n| `length` | number | No | Internal cargo-space length, along x (from the front wall toward the door). Unit: `cm`. |\n| `width` | number | No | Internal cargo-space width, along z. Unit: `cm`. |\n| `height` | number | No | Internal cargo-space height, along y. Unit: `cm`. |\n| `max_weight` | number | No | Maximum payload. Unit: `kg`. |\n| `floor_load_capacity` | number | No | Floor load capacity: 0 or more (0 = no limit). Unit: `kg/m²`. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). Default: `false`. |\n| `aux_deck_height` | number | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. At most 32 entries. |\n| `axle_positions` | number[] | No | Axle (support point) positions measured from the front of the cargo area; negative = in front of it. Same length as axle_max_loads. Each between −300 (an axle ahead of the cargo area, e.g. a rigid truck's front axle under the cab) and the vehicle length. At most 10 entries. Unit: `cm`. Requires access (`axle_loads`). |\n| `axle_max_loads` | number[] | No | Maximum cargo load per axle, in the order of axle_positions. Each greater than 0 and at most 100000. At most 10 entries. Unit: `kg`. Requires access (`axle_loads`). |\n| `cog_range_x` | number | No | Allowed centre-of-gravity deviation from the middle of the length: 0 to length / 2. Unit: `cm`. Requires access (`axle_loads`). |\n| `cog_range_y` | number | No | Allowed centre-of-gravity deviation from the middle of the width: 0 to width / 2. Unit: `cm`. Requires access (`axle_loads`). |\n| `tractor_weight` | number | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `empty_trailer_weight` | number | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. Requires access (`axle_loads`). |\n| `front_axle_to_middle_axle` | number | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_tractor_cog` | number | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. Requires access (`axle_loads`). |\n| `front_axle_to_kingpin` | number | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. Requires access (`axle_loads`). |\n| `kingpin_to_rear_axle` | number | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_cog_to_rear_axle` | number | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. Requires access (`axle_loads`). |\n| `trailer_start_to_kingpin` | number | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. Requires access (`axle_loads`). |\n| `max_middle_axle_weight` | number | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. Requires access (`axle_loads`). |\n| `max_rear_axle_weight` | number | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. Requires access (`axle_loads`). |\n\n### Rect\n\nA rectangle on the vehicle floor (min-corner origin).\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n## Example request\n\n```bash\ncurl -X PATCH \"https://runner.cargoduo.com/v1/vehicles/veh_118\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"max_weight\": 25000\n  }'\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/vehicles/veh_118\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"max_weight\": 25000\n  }),\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.patch(\n    \"https://runner.cargoduo.com/v1/vehicles/veh_118\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    json={\n        \"max_weight\": 25000,\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"id\": \"veh_118\",\n  \"object\": \"vehicle\",\n  \"kind\": \"custom\",\n  \"name\": \"Mega trailer 13.6 m\",\n  \"type\": \"trailer\",\n  \"length\": 1360,\n  \"width\": 248,\n  \"height\": 300,\n  \"max_weight\": 25000,\n  \"floor_load_capacity\": null,\n  \"axle_positions\": [],\n  \"axle_max_loads\": [],\n  \"cog_range_x\": null,\n  \"cog_range_y\": null,\n  \"aux_deck\": false,\n  \"aux_deck_height\": null,\n  \"aux_deck_max_weight\": null,\n  \"tractor_weight\": null,\n  \"empty_trailer_weight\": null,\n  \"front_axle_to_middle_axle\": null,\n  \"front_axle_to_tractor_cog\": null,\n  \"front_axle_to_kingpin\": null,\n  \"kingpin_to_rear_axle\": null,\n  \"trailer_cog_to_rear_axle\": null,\n  \"trailer_start_to_kingpin\": null,\n  \"max_middle_axle_weight\": null,\n  \"max_rear_axle_weight\": null,\n  \"blocked_areas\": [],\n  \"created_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"updated_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"created_at\": \"2026-10-02T08:41:17Z\"\n}\n```\n\n## Responses\n\n### 200 OK\n\nThe vehicle after the change. Returns `SavedVehicle`.\n\n### 400 Bad request\n\n| Code | Meaning |\n|---|---|\n| `invalid_json` | The body is not valid JSON or a field has the wrong type. |\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `preset_read_only` | Presets cannot be changed or deleted. |\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `vehicle_not_found` | No vehicle with this id among the presets or your organization's vehicles. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The SavedVehicle object\n\nA vehicle your organization can name with `vehicle_id`: a preset (`kind` `preset`, read-only) or one of your own (`kind` `custom`, `veh_…`). Your own vehicles are the fleet the app shows.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Preset id, or veh_… for your own vehicles. |\n| `object` | string | No | One of: `vehicle`. |\n| `kind` | string | No | `preset` = the predefined catalogue; `custom` = your organization's. One of: `preset`, `custom`. |\n| `type` | string | No | Vehicle type. Only the types listed in GET /v1/limits (allowed_vehicle_types) are accepted. One of: `truck`, `trailer`, `flatbed`, `container`, `pallet`, `aircraft`, `ship`. |\n| `name` | string | No | Display name. |\n| `length` | number | No | Internal cargo-space length, along x (from the front wall toward the door). Unit: `cm`. |\n| `width` | number | No | Internal cargo-space width, along z. Unit: `cm`. |\n| `height` | number | No | Internal cargo-space height, along y. Unit: `cm`. |\n| `max_weight` | number | No | Maximum payload. Unit: `kg`. |\n| `floor_load_capacity` | number \\| null | No | Floor load capacity: 0 or more (0 = no limit). Unit: `kg/m²`. |\n| `axle_positions` | number[] | No | Axle (support point) positions measured from the front of the cargo area; negative = in front of it. Same length as axle_max_loads. Each between −300 (an axle ahead of the cargo area, e.g. a rigid truck's front axle under the cab) and the vehicle length. Unit: `cm`. |\n| `axle_max_loads` | number[] | No | Maximum cargo load per axle, in the order of axle_positions. Each greater than 0 and at most 100000. Unit: `kg`. |\n| `cog_range_x` | number \\| null | No | Allowed centre-of-gravity deviation from the middle of the length: 0 to length / 2. Unit: `cm`. |\n| `cog_range_y` | number \\| null | No | Allowed centre-of-gravity deviation from the middle of the width: 0 to width / 2. Unit: `cm`. |\n| `aux_deck` | boolean | No | The vehicle has a second deck (double-decker). |\n| `aux_deck_height` | number \\| null | No | Second deck height above the floor. Required when `aux_deck` is `true`. Unit: `cm`. |\n| `aux_deck_max_weight` | number \\| null | No | Load limit of the second deck. Required when `aux_deck` is `true`. Unit: `kg`. |\n| `tractor_weight` | number \\| null | No | Semi-trailer axle model: tractor unit mass. Unit: `kg`. |\n| `empty_trailer_weight` | number \\| null | No | Semi-trailer axle model: unladen trailer mass. Unit: `kg`. |\n| `front_axle_to_middle_axle` | number \\| null | No | Semi-trailer axle model: tractor front axle to drive (middle) axle. Unit: `cm`. |\n| `front_axle_to_tractor_cog` | number \\| null | No | Semi-trailer axle model: front axle to tractor centre of gravity. Unit: `cm`. |\n| `front_axle_to_kingpin` | number \\| null | No | Semi-trailer axle model: front axle to kingpin (fifth wheel). Unit: `cm`. |\n| `kingpin_to_rear_axle` | number \\| null | No | Semi-trailer axle model: kingpin to trailer rear axle group. Unit: `cm`. |\n| `trailer_cog_to_rear_axle` | number \\| null | No | Semi-trailer axle model: trailer centre of gravity to rear axle group. Unit: `cm`. |\n| `trailer_start_to_kingpin` | number \\| null | No | Semi-trailer axle model: trailer front to kingpin. Unit: `cm`. |\n| `max_middle_axle_weight` | number \\| null | No | Semi-trailer axle model: legal drive (middle) axle limit. Unit: `kg`. |\n| `max_rear_axle_weight` | number \\| null | No | Semi-trailer axle model: legal rear axle group limit. Unit: `kg`. |\n| `blocked_areas` | Rect[] | No | Floor zones nothing may stand on (wheel arches, damage, reserved space). Each must lie on the vehicle floor. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string \\| null | No | RFC 3339; `null` for presets. |\n\n### Rect\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `x` | number | Yes | Distance from the front wall to the rectangle's near edge. Unit: `cm`. |\n| `z` | number | Yes | Distance from the right wall (z = 0; right as seen from the door looking in) to the rectangle's near edge. Unit: `cm`. |\n| `length` | number | Yes | Extent along x. Unit: `cm`. |\n| `width` | number | Yes | Extent along z. Unit: `cm`. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n\n## Related\n\n- [Get a vehicle](https://runner.cargoduo.com/api/public/api-docs/pages/get-vehicle.md): A preset or one of your vehicles.\n"},{"slug":"delete-vehicle","title":"Delete a vehicle","section":"API reference","group":"Vehicles","summary":"Deletes one of your vehicles.","keywords":"delete /v1/vehicles/{id} deletevehicle","method":"DELETE","path":"/v1/vehicles/{id}","operation_id":"deleteVehicle","related":["list-vehicles"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/delete-vehicle.md","markdown":"# Delete a vehicle\n\nDeletes one of your vehicles.\n\n```http\nDELETE https://runner.cargoduo.com/v1/vehicles/{id}\n```\n\nPresets are read-only. Free: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | A preset id or one of your vehicles (veh_…). |\n\n## Example request\n\n```bash\ncurl -X DELETE \"https://runner.cargoduo.com/v1/vehicles/veh_118\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/vehicles/veh_118\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.delete(\n    \"https://runner.cargoduo.com/v1/vehicles/veh_118\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"id\": \"veh_118\",\n  \"object\": \"vehicle\",\n  \"deleted\": true\n}\n```\n\n## Responses\n\n### 200 OK\n\nDeleted. Returns `Deleted`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `preset_read_only` | Presets cannot be changed or deleted. |\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `vehicle_not_found` | No vehicle with this id among the presets or your organization's vehicles. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Deleted object\n\nAnswer to a DELETE.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No |  |\n| `object` | string | No | One of: `vehicle`, `template`, `project`. |\n| `deleted` | boolean | No | Always `true`. |\n\n\n## Related\n\n- [List vehicles](https://runner.cargoduo.com/api/public/api-docs/pages/list-vehicles.md): Every vehicle you can name with vehicle_id: the presets (containers, trailers, trucks, pallets of the types your organization may use), then your organization's own vehicles.\n"},{"slug":"list-templates","title":"List templates","section":"API reference","group":"Templates","summary":"Your organization's cargo templates (saved items), newest first.","keywords":"get /v1/templates listtemplates","method":"GET","path":"/v1/templates","operation_id":"listTemplates","related":["create-template","saved-data"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/list-templates.md","markdown":"# List templates\n\nYour organization's cargo templates (saved items), newest first.\n\n```http\nGET https://runner.cargoduo.com/v1/templates\n```\n\nName one in an item with template_id. Free: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Query parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `limit` | integer | No | Page size. Default: `20`. 1–100. |\n| `cursor` | string | No | next_cursor of the previous page. |\n| `q` | string | No | Only templates whose label contains this text. |\n| `created_by` | string | No | Only what this member created: `me` (the key's creator) or a member id (`usr_…`). |\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/templates?created_by=me\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/templates?created_by=me\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/templates\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    params={\n        \"created_by\": \"me\",\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"tpl_43\",\n      \"object\": \"template\",\n      \"label\": \"Carton A\",\n      \"shape\": \"box\",\n      \"length\": 60,\n      \"width\": 40,\n      \"height\": 40,\n      \"radius\": 0,\n      \"weight\": 12,\n      \"stackable\": true,\n      \"max_stack_weight\": 0,\n      \"rotation\": \"upright\",\n      \"hazardous\": false,\n      \"priority\": 0,\n      \"color\": \"#f59e0b\",\n      \"nesting_height\": 0,\n      \"created_by\": {\n        \"id\": \"usr_7\",\n        \"name\": \"Elif Kaya\"\n      },\n      \"updated_by\": {\n        \"id\": \"usr_7\",\n        \"name\": \"Elif Kaya\"\n      },\n      \"created_at\": \"2026-09-28T14:05:52Z\",\n      \"updated_at\": \"2026-09-28T14:05:52Z\"\n    },\n    {\n      \"id\": \"tpl_42\",\n      \"object\": \"template\",\n      \"label\": \"Euro pallet\",\n      \"shape\": \"box\",\n      \"length\": 120,\n      \"width\": 80,\n      \"height\": 140,\n      \"radius\": 0,\n      \"weight\": 450,\n      \"stackable\": false,\n      \"max_stack_weight\": 0,\n      \"rotation\": \"all\",\n      \"hazardous\": false,\n      \"priority\": 0,\n      \"color\": \"#3b82f6\",\n      \"nesting_height\": 0,\n      \"created_by\": {\n        \"id\": \"usr_7\",\n        \"name\": \"Elif Kaya\"\n      },\n      \"updated_by\": {\n        \"id\": \"usr_7\",\n        \"name\": \"Elif Kaya\"\n      },\n      \"created_at\": \"2026-09-28T14:05:52Z\",\n      \"updated_at\": \"2026-09-28T14:05:52Z\"\n    }\n  ],\n  \"next_cursor\": \"\"\n}\n```\n\n## Responses\n\n### 200 OK\n\nOne page of templates. Returns `TemplateList`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The TemplateList object\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `data` | Template[] | No | Newest first. |\n| `next_cursor` | string | No | Pass as ?cursor= for the next page; empty on the last page. |\n\n### Template\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Template id (tpl_…). |\n| `object` | string | No | One of: `template`. |\n| `label` | string | No | Display label. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | No | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | No | Weight of ONE unit. Unit: `kg`. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Unit: `cm`. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string | No | RFC 3339. |\n| `updated_at` | string | No | RFC 3339. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n\n## Related\n\n- [Create a template](https://runner.cargoduo.com/api/public/api-docs/pages/create-template.md): Saves an item as a template (it shows in the app too).\n- [Saved data \u0026 projects](https://runner.cargoduo.com/api/public/api-docs/pages/saved-data.md): Vehicles, templates and projects shared with the app.\n"},{"slug":"get-template","title":"Get a template","section":"API reference","group":"Templates","summary":"One template.","keywords":"get /v1/templates/{id} gettemplate","method":"GET","path":"/v1/templates/{id}","operation_id":"getTemplate","related":["list-templates","update-template"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/get-template.md","markdown":"# Get a template\n\nOne template.\n\n```http\nGET https://runner.cargoduo.com/v1/templates/{id}\n```\n\nFree: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Template id (tpl_…). |\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/templates/tpl_42\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/templates/tpl_42\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/templates/tpl_42\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"id\": \"tpl_42\",\n  \"object\": \"template\",\n  \"label\": \"Euro pallet\",\n  \"shape\": \"box\",\n  \"length\": 120,\n  \"width\": 80,\n  \"height\": 140,\n  \"radius\": 0,\n  \"weight\": 450,\n  \"stackable\": false,\n  \"max_stack_weight\": 0,\n  \"rotation\": \"all\",\n  \"hazardous\": false,\n  \"priority\": 0,\n  \"color\": \"#3b82f6\",\n  \"nesting_height\": 0,\n  \"created_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"updated_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"created_at\": \"2026-09-28T14:05:52Z\",\n  \"updated_at\": \"2026-09-28T14:05:52Z\"\n}\n```\n\n## Responses\n\n### 200 OK\n\nThe template. Returns `Template`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `template_not_found` | No template with this id in your organization. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Template object\n\nA saved item (the app's cargo template). Name it in an item with `template_id`.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Template id (tpl_…). |\n| `object` | string | No | One of: `template`. |\n| `label` | string | No | Display label. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | No | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | No | Weight of ONE unit. Unit: `kg`. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Unit: `cm`. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string | No | RFC 3339. |\n| `updated_at` | string | No | RFC 3339. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n\n## Related\n\n- [List templates](https://runner.cargoduo.com/api/public/api-docs/pages/list-templates.md): Your organization's cargo templates (saved items), newest first.\n- [Update a template](https://runner.cargoduo.com/api/public/api-docs/pages/update-template.md): Changes a template.\n"},{"slug":"create-template","title":"Create a template","section":"API reference","group":"Templates","summary":"Saves an item as a template (it shows in the app too).","keywords":"post /v1/templates createtemplate","method":"POST","path":"/v1/templates","operation_id":"createTemplate","related":["list-templates","create-pack-job"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/create-template.md","markdown":"# Create a template\n\nSaves an item as a template (it shows in the app too).\n\n```http\nPOST https://runner.cargoduo.com/v1/templates\n```\n\nSame fields and checks as a /v1/pack item. Free: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Body parameters\n\nA template: the /v1/pack `Item` fields without the per-load ones (`id`, `quantity`, `group`), validated the same way.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `label` | string | Yes | Display label. Max 255 characters. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. Default: `\"box\"`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | Yes | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | Yes | Weight of ONE unit. Unit: `kg`. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. Default: `true`. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Default: `0`. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. Default: `\"all\"`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. Default: `false`. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. Default: `0`. |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Default: `0`. Unit: `cm`. |\n\n## Example request\n\n```bash\ncurl -X POST \"https://runner.cargoduo.com/v1/templates\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"label\": \"Euro pallet\",\n    \"length\": 120,\n    \"width\": 80,\n    \"height\": 140,\n    \"weight\": 450,\n    \"stackable\": false,\n    \"color\": \"#3b82f6\"\n  }'\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/templates\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"label\": \"Euro pallet\",\n    \"length\": 120,\n    \"width\": 80,\n    \"height\": 140,\n    \"weight\": 450,\n    \"stackable\": false,\n    \"color\": \"#3b82f6\"\n  }),\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.post(\n    \"https://runner.cargoduo.com/v1/templates\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    json={\n        \"label\": \"Euro pallet\",\n        \"length\": 120,\n        \"width\": 80,\n        \"height\": 140,\n        \"weight\": 450,\n        \"stackable\": False,\n        \"color\": \"#3b82f6\",\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`201` \n\n```json\n{\n  \"id\": \"tpl_42\",\n  \"object\": \"template\",\n  \"label\": \"Euro pallet\",\n  \"shape\": \"box\",\n  \"length\": 120,\n  \"width\": 80,\n  \"height\": 140,\n  \"radius\": 0,\n  \"weight\": 450,\n  \"stackable\": false,\n  \"max_stack_weight\": 0,\n  \"rotation\": \"all\",\n  \"hazardous\": false,\n  \"priority\": 0,\n  \"color\": \"#3b82f6\",\n  \"nesting_height\": 0,\n  \"created_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"updated_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"created_at\": \"2026-09-28T14:05:52Z\",\n  \"updated_at\": \"2026-09-28T14:05:52Z\"\n}\n```\n\n## Responses\n\n### 201 \n\nThe new template. Returns `Template`.\n\n### 400 Bad request\n\n| Code | Meaning |\n|---|---|\n| `invalid_json` | The body is not valid JSON or a field has the wrong type. |\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 413 Payload too large\n\n| Code | Meaning |\n|---|---|\n| `payload_too_large` | The request body exceeds 10 MB. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Template object\n\nA saved item (the app's cargo template). Name it in an item with `template_id`.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Template id (tpl_…). |\n| `object` | string | No | One of: `template`. |\n| `label` | string | No | Display label. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | No | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | No | Weight of ONE unit. Unit: `kg`. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Unit: `cm`. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string | No | RFC 3339. |\n| `updated_at` | string | No | RFC 3339. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n\n## Related\n\n- [List templates](https://runner.cargoduo.com/api/public/api-docs/pages/list-templates.md): Your organization's cargo templates (saved items), newest first.\n- [Create a packing job](https://runner.cargoduo.com/api/public/api-docs/pages/create-pack-job.md): Validates the request, reserves the worst-case price from your credits and queues a job.\n"},{"slug":"update-template","title":"Update a template","section":"API reference","group":"Templates","summary":"Changes a template.","keywords":"patch /v1/templates/{id} updatetemplate","method":"PATCH","path":"/v1/templates/{id}","operation_id":"updateTemplate","related":["get-template"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/update-template.md","markdown":"# Update a template\n\nChanges a template.\n\n```http\nPATCH https://runner.cargoduo.com/v1/templates/{id}\n```\n\nItems already in projects keep their own copy. Free: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Template id (tpl_…). |\n\n## Body parameters\n\nThe fields to change. Absent fields keep their value.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `label` | string | No | Display label. Max 255 characters. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. Default: `\"box\"`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | No | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | No | Weight of ONE unit. Unit: `kg`. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. Default: `true`. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Default: `0`. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. Default: `\"all\"`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. Default: `false`. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. Default: `0`. |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Default: `0`. Unit: `cm`. |\n\n## Example request\n\n```bash\ncurl -X PATCH \"https://runner.cargoduo.com/v1/templates/tpl_42\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"weight\": 480\n  }'\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/templates/tpl_42\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"weight\": 480\n  }),\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.patch(\n    \"https://runner.cargoduo.com/v1/templates/tpl_42\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    json={\n        \"weight\": 480,\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"id\": \"tpl_42\",\n  \"object\": \"template\",\n  \"label\": \"Euro pallet\",\n  \"shape\": \"box\",\n  \"length\": 120,\n  \"width\": 80,\n  \"height\": 140,\n  \"radius\": 0,\n  \"weight\": 480,\n  \"stackable\": false,\n  \"max_stack_weight\": 0,\n  \"rotation\": \"all\",\n  \"hazardous\": false,\n  \"priority\": 0,\n  \"color\": \"#3b82f6\",\n  \"nesting_height\": 0,\n  \"created_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"updated_by\": {\n    \"id\": \"usr_7\",\n    \"name\": \"Elif Kaya\"\n  },\n  \"created_at\": \"2026-09-28T14:05:52Z\",\n  \"updated_at\": \"2026-10-19T09:12:03Z\"\n}\n```\n\n## Responses\n\n### 200 OK\n\nThe template after the change. Returns `Template`.\n\n### 400 Bad request\n\n| Code | Meaning |\n|---|---|\n| `invalid_json` | The body is not valid JSON or a field has the wrong type. |\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `template_not_found` | No template with this id in your organization. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Template object\n\nA saved item (the app's cargo template). Name it in an item with `template_id`.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Template id (tpl_…). |\n| `object` | string | No | One of: `template`. |\n| `label` | string | No | Display label. |\n| `shape` | string | No | Item shape. `cylinder` and `drum` use `radius` + `height` instead of `length` + `width`; `pallet` and `drum` always stay upright. One of: `box`, `cylinder`, `pallet`, `bag`, `drum`. |\n| `length` | number | No | Length. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `width` | number | No | Width. Required unless `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `height` | number | No | Height (the vertical side when the item stands as defined). Unit: `cm`. |\n| `radius` | number | No | Radius. Required when `shape` is `cylinder` or `drum`. Unit: `cm`. |\n| `weight` | number | No | Weight of ONE unit. Unit: `kg`. |\n| `stackable` | boolean | No | Other items may be placed on top of this one. |\n| `max_stack_weight` | number | No | Maximum weight that may rest on one unit. 0 = unlimited. Unit: `kg`. |\n| `rotation` | string | No | `all` = any of the six orientations; `upright` = may only turn around the vertical axis (this side up); `fixed` = never rotated. One of: `all`, `upright`, `fixed`. |\n| `hazardous` | boolean | No | Dangerous goods: kept low and separated from other hazardous items. |\n| `priority` | integer | No | Load priority. Higher = loaded first and kept when space runs out. 0 = none. |\n| `color` | string | No | Display colour as #rrggbb (viewer and PDF only). |\n| `nesting_height` | number | No | Telescoping overlap when identical units stack (nested crates, buckets). Unit: `cm`. |\n| `created_by` | UserRef \\| null | No | The member who created it (for API calls: the member who created the key). `null` when unknown. |\n| `updated_by` | UserRef \\| null | No | The member who last changed it. |\n| `created_at` | string | No | RFC 3339. |\n| `updated_at` | string | No | RFC 3339. |\n\n### UserRef\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No | Member id (usr_…). Filter lists with ?created_by=. |\n| `name` | string | No |  |\n\n\n## Related\n\n- [Get a template](https://runner.cargoduo.com/api/public/api-docs/pages/get-template.md): One template.\n"},{"slug":"delete-template","title":"Delete a template","section":"API reference","group":"Templates","summary":"Deletes a template.","keywords":"delete /v1/templates/{id} deletetemplate","method":"DELETE","path":"/v1/templates/{id}","operation_id":"deleteTemplate","related":["list-templates"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/delete-template.md","markdown":"# Delete a template\n\nDeletes a template.\n\n```http\nDELETE https://runner.cargoduo.com/v1/templates/{id}\n```\n\nItems already in projects stay. Free: no credits are charged.\n\n## Authentication\n\nBearer API key with the `resources:write` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Path parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | Yes | Template id (tpl_…). |\n\n## Example request\n\n```bash\ncurl -X DELETE \"https://runner.cargoduo.com/v1/templates/tpl_42\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/templates/tpl_42\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.delete(\n    \"https://runner.cargoduo.com/v1/templates/tpl_42\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"id\": \"tpl_42\",\n  \"object\": \"template\",\n  \"deleted\": true\n}\n```\n\n## Responses\n\n### 200 OK\n\nDeleted. Returns `Deleted`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 404 Not found\n\n| Code | Meaning |\n|---|---|\n| `template_not_found` | No template with this id in your organization. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Deleted object\n\nAnswer to a DELETE.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `id` | string | No |  |\n| `object` | string | No | One of: `vehicle`, `template`, `project`. |\n| `deleted` | boolean | No | Always `true`. |\n\n\n## Related\n\n- [List templates](https://runner.cargoduo.com/api/public/api-docs/pages/list-templates.md): Your organization's cargo templates (saved items), newest first.\n"},{"slug":"get-limits","title":"Get limits","section":"API reference","group":"Account","summary":"The limits that apply to your organization and the rate card new jobs are priced with.","keywords":"get /v1/limits getlimits","method":"GET","path":"/v1/limits","operation_id":"getLimits","related":["limits","credits"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/get-limits.md","markdown":"# Get limits\n\nThe limits that apply to your organization and the rate card new jobs are priced with.\n\n```http\nGET https://runner.cargoduo.com/v1/limits\n```\n\n## Authentication\n\nBearer API key with the `account:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/limits\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/limits\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/limits\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"max_items_per_request\": 2000,\n  \"max_vehicles_per_request\": 20,\n  \"max_time_limit_seconds\": 90,\n  \"allowed_vehicle_types\": [\n    \"truck\",\n    \"trailer\",\n    \"container\",\n    \"pallet\"\n  ],\n  \"concurrency\": 1,\n  \"max_queued_jobs\": 20,\n  \"rate_limits\": {\n    \"per_minute\": 60,\n    \"per_hour\": 1000,\n    \"per_day\": 5000\n  },\n  \"retention_days\": 14,\n  \"rate_card\": {\n    \"version\": 1,\n    \"per_job\": 0.06,\n    \"per_vehicle\": 0.2,\n    \"per_compute_second\": 0.005,\n    \"large_load_threshold\": 500,\n    \"per_hundred_items_over\": 0.02\n  }\n}\n```\n\n## Responses\n\n### 200 OK\n\nLimits and rate card. Returns `Limits`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Limits object\n\nYour organization's effective limits and the rate card new jobs are priced with.\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `max_items_per_request` | integer | No | Most item units (sum of quantities) per job. |\n| `max_vehicles_per_request` | integer | No | Most vehicles a job may list and load (auto-added ones included). |\n| `max_time_limit_seconds` | integer | No | Compute budget ceiling per job. |\n| `allowed_vehicle_types` | string[] | No | Vehicle types the API accepts. |\n| `concurrency` | integer | No | Jobs of your organization computing at the same time; more wait in the queue. |\n| `max_queued_jobs` | integer | No | Queued + running jobs per organization. |\n| `rate_limits` | RateLimits | No | Requests per window, per key and per organization. |\n| `retention_days` | integer | No | Days request/result bodies and PDF links are kept. |\n| `rate_card` | RateCard | No | Prices in credits (1 credit = 1 USD). |\n\n### RateLimits\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `per_minute` | integer | No |  |\n| `per_hour` | integer | No |  |\n| `per_day` | integer | No |  |\n\n### RateCard\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `version` | integer | No |  |\n| `per_job` | number | No | Unit: `credits`. |\n| `per_vehicle` | number | No | Per vehicle actually loaded. Unit: `credits`. |\n| `per_compute_second` | number | No | Pro-rated per millisecond. Unit: `credits`. |\n| `large_load_threshold` | integer | No | Item units included before the surcharge applies. |\n| `per_hundred_items_over` | number | No | Per started 100 units above the threshold. Unit: `credits`. |\n\n\n## Related\n\n- [Rate limits \u0026 limits](https://runner.cargoduo.com/api/public/api-docs/pages/limits.md): Request rates, job size, concurrency and retention.\n- [Credits \u0026 pricing](https://runner.cargoduo.com/api/public/api-docs/pages/credits.md): What a job costs and where credits come from.\n"},{"slug":"get-balance","title":"Get the balance","section":"API reference","group":"Account","summary":"Available, plan, purchased and reserved credits.","keywords":"get /v1/balance getbalance","method":"GET","path":"/v1/balance","operation_id":"getBalance","related":["credits","get-usage"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/get-balance.md","markdown":"# Get the balance\n\nAvailable, plan, purchased and reserved credits.\n\n```http\nGET https://runner.cargoduo.com/v1/balance\n```\n\n## Authentication\n\nBearer API key with the `account:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/balance\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/balance\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/balance\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"available_credits\": 147.75,\n  \"subscription_credits\": 98.5,\n  \"subscription_expires_at\": \"2026-11-07T00:00:00Z\",\n  \"purchased_credits\": 50,\n  \"reserved_credits\": 0.75\n}\n```\n\n## Responses\n\n### 200 OK\n\nBalance. Returns `Balance`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Balance object\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `available_credits` | number | No | What new jobs can use (subscription + purchased − reserved). Unit: `credits`. |\n| `subscription_credits` | number | No | Monthly subscription credits left (spent first; expire at subscription_expires_at). Unit: `credits`. |\n| `subscription_expires_at` | string \\| null | No | When the monthly credits expire (RFC 3339). |\n| `purchased_credits` | number | No | Credits from packs (never expire). Unit: `credits`. |\n| `reserved_credits` | number | No | Held by queued/running jobs. Unit: `credits`. |\n\n\n## Related\n\n- [Credits \u0026 pricing](https://runner.cargoduo.com/api/public/api-docs/pages/credits.md): What a job costs and where credits come from.\n- [Get usage](https://runner.cargoduo.com/api/public/api-docs/pages/get-usage.md): Requests, jobs, credits, compute seconds and vehicles per UTC day.\n"},{"slug":"get-usage","title":"Get usage","section":"API reference","group":"Account","summary":"Requests, jobs, credits, compute seconds and vehicles per UTC day.","keywords":"get /v1/usage getusage","method":"GET","path":"/v1/usage","operation_id":"getUsage","related":["get-balance","credits"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/get-usage.md","markdown":"# Get usage\n\nRequests, jobs, credits, compute seconds and vehicles per UTC day.\n\n```http\nGET https://runner.cargoduo.com/v1/usage\n```\n\nWithout from/to: the last 30 days. At most 92 days per call.\n\n## Authentication\n\nBearer API key with the `account:read` scope. See [Authentication](https://runner.cargoduo.com/api/public/api-docs/pages/authentication.md).\n\n## Query parameters\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `from` | string (date) | No | First day, YYYY-MM-DD (UTC). |\n| `to` | string (date) | No | Last day, YYYY-MM-DD (UTC). Defaults to today. |\n\n## Example request\n\n```bash\ncurl -X GET \"https://runner.cargoduo.com/v1/usage?from=2026-10-17\u0026to=2026-10-19\" \\\n  -H \"Authorization: Bearer $CARGODUO_API_KEY\"\n```\n\n```javascript\nconst res = await fetch(\"https://runner.cargoduo.com/v1/usage?from=2026-10-17\u0026to=2026-10-19\", {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.CARGODUO_API_KEY}`,\n  },\n});\n\nconst data = await res.json();\nconsole.log(res.status, data);\n```\n\n```python\nimport os\nimport requests\n\nresponse = requests.get(\n    \"https://runner.cargoduo.com/v1/usage\",\n    headers={\"Authorization\": f\"Bearer {os.environ['CARGODUO_API_KEY']}\"},\n    params={\n        \"from\": \"2026-10-17\",\n        \"to\": \"2026-10-19\",\n    },\n)\nprint(response.status_code, response.json())\n```\n\n## Example response\n\n`200` OK\n\n```json\n{\n  \"data\": [\n    {\n      \"date\": \"2026-10-17\",\n      \"requests\": 41,\n      \"jobs\": 9,\n      \"credits\": 2.931,\n      \"compute_seconds\": 38.2,\n      \"vehicles\": 11\n    },\n    {\n      \"date\": \"2026-10-18\",\n      \"requests\": 12,\n      \"jobs\": 3,\n      \"credits\": 0.829,\n      \"compute_seconds\": 9.8,\n      \"vehicles\": 3\n    },\n    {\n      \"date\": \"2026-10-19\",\n      \"requests\": 27,\n      \"jobs\": 6,\n      \"credits\": 1.867,\n      \"compute_seconds\": 21.4,\n      \"vehicles\": 7\n    }\n  ]\n}\n```\n\n## Responses\n\n### 200 OK\n\nOne row per day. Returns `Usage`.\n\n### 401 Unauthorized\n\n| Code | Meaning |\n|---|---|\n| `unauthorized` | Missing, malformed or unknown API key. |\n| `key_revoked` | The key was revoked. |\n| `key_expired` | The key passed its expiry date. |\n\n### 403 Forbidden\n\n| Code | Meaning |\n|---|---|\n| `ip_not_allowed` | The calling IP is not on the key's allowlist. |\n| `subscription_inactive` | The organization has no active subscription that includes the API. |\n| `account_inactive` | The organization is suspended, or has no owner whose account is active. |\n| `scope_missing` | The key lacks a scope this endpoint needs. |\n\n### 422 Unprocessable entity\n\n| Code | Meaning |\n|---|---|\n| `validation_failed` | The request is well-formed JSON but describes an invalid job. details[] lists every problem. |\n\n### 429 Too many requests\n\n| Code | Meaning |\n|---|---|\n| `rate_limited` | Too many requests in the current minute, hour or day (per key or per organization), or more than 60 failed authentications per minute from your IP address. |\n\n### 500 Internal server error\n\n| Code | Meaning |\n|---|---|\n| `internal_error` | Something failed on our side. |\n\n### 503 Service unavailable\n\n| Code | Meaning |\n|---|---|\n| `api_disabled` | The API is temporarily disabled (maintenance) or switched off for the organization. |\n\nEvery error has the same envelope. See [Errors](https://runner.cargoduo.com/api/public/api-docs/pages/errors.md).\n\n## The Usage object\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `data` | UsageDay[] | No |  |\n\n### UsageDay\n\n| Name | Type | Required | Description |\n|---|---|---|---|\n| `date` | string | No | UTC date YYYY-MM-DD. |\n| `requests` | integer | No |  |\n| `jobs` | integer | No |  |\n| `credits` | number | No | Unit: `credits`. |\n| `compute_seconds` | number | No | Unit: `s`. |\n| `vehicles` | integer | No | Vehicles loaded. |\n\n\n## Related\n\n- [Get the balance](https://runner.cargoduo.com/api/public/api-docs/pages/get-balance.md): Available, plan, purchased and reserved credits.\n- [Credits \u0026 pricing](https://runner.cargoduo.com/api/public/api-docs/pages/credits.md): What a job costs and where credits come from.\n"},{"slug":"agent-prompt","title":"Agent prompt","section":"AI tools","summary":"One line that sets up a coding agent to integrate the API.","keywords":"ai agent claude chatgpt cursor codex copilot prompt llm copy page markdown","related":["llms-txt","quickstart"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/agent-prompt.md","markdown":"# Agent prompt\n\nOne line that sets up a coding agent to integrate the API.\n\n```prompt\nIntegrate the CargoDuo packing API into this project. Fetch https://runner.cargoduo.com/api/public/api-docs/llms-full.txt and follow it.\n```\n\nThe playbook gives the agent auth, the request shape, waiting, errors, retries, idempotency, limits and credits, with a worked example in cURL, Node.js and Python.\n\n## Any page, as Markdown\n\n```text\nhttps://runner.cargoduo.com/api/public/api-docs/pages/\u003cslug\u003e.md\n```\n\n**Copy page** on every docs page copies it as Markdown, or opens it in ChatGPT or Claude. Add `?lang=` for a translation: `es`, `tr`, `de`, `zh-CN`, `fr`, `pl`, `ru`, `vi`.\n\n## Related\n\n- [llms.txt](https://runner.cargoduo.com/api/public/api-docs/pages/llms-txt.md): Machine-readable docs: llms.txt, llms-full.txt, OpenAPI and Markdown pages.\n- [Quickstart](https://runner.cargoduo.com/api/public/api-docs/pages/quickstart.md): Create a key, send one request, read the plan.\n"},{"slug":"llms-txt","title":"llms.txt","section":"AI tools","summary":"Machine-readable docs: llms.txt, llms-full.txt, OpenAPI and Markdown pages.","keywords":"llms.txt llms-full.txt openapi markdown machine readable ai","related":["agent-prompt","api-reference"],"url":"https://runner.cargoduo.com/api/public/api-docs/pages/llms-txt.md","markdown":"# llms.txt\n\nMachine-readable docs: llms.txt, llms-full.txt, OpenAPI and Markdown pages.\n\n| File | Holds |\n|---|---|\n| [llms.txt](https://runner.cargoduo.com/api/public/api-docs/llms.txt) | Index of every page, for language models. |\n| [llms-full.txt](https://runner.cargoduo.com/api/public/api-docs/llms-full.txt) | Integration playbook for coding agents. |\n| [openapi.json](https://runner.cargoduo.com/api/public/api-docs/openapi.json) | OpenAPI 3.1 document. |\n| [pages.json](https://runner.cargoduo.com/api/public/api-docs/pages.json) | Every page with its Markdown. |\n| `pages/\u003cslug\u003e.md` | One docs page as Markdown. |\n\nPublic, no key. Generated from the same source as these pages, with live limits.\n\n## Related\n\n- [Agent prompt](https://runner.cargoduo.com/api/public/api-docs/pages/agent-prompt.md): One line that sets up a coding agent to integrate the API.\n- [Overview](https://runner.cargoduo.com/api/public/api-docs/pages/api-reference.md): Base URL, conventions and every endpoint.\n"}]}