{"openapi":"3.1.0","info":{"title":"Crowd Cow 3PL partner API","version":"2026-11-06","summary":"Push orders and SKUs, track fulfillment, and quote shipping.","description":"This page describes the API as it will be when the current build-out is\nfinished. Anything marked **Coming** is not available yet: a marked\noperation is not there to call, and a marked field or parameter is ignored\nif you send it and absent from what you get back. Everything else works\nnow. Field names, filters and error\nshapes here are the contract, and changes to them are additive. The live\nreference is at https://3pl.crowdcow.com/api-reference.\n\nPush inventory and orders, reschedule, quote shipping and read delivery\ndates, and read back your shipments, inventory movements, reports and\ninvoices — programmatically. Base URL\n`https://www.crowdcow.com/api/fulfillment/v1`.\n\n## Authentication\n\nEvery request needs your key, in either header — they are the same\ncredential. Keys belong to an organization, and an organization can hold as\nmany as it has integrations; one key per integration makes a compromised key\ncheap to retire.\n\n```\nX-Api-Key: \u003cYOUR_TOKEN\u003e\nAuthorization: Bearer \u003cYOUR_TOKEN\u003e\n```\n\nA missing or revoked key returns **401**. Quote, Shipment Calendar,\nShipments and the delivery performance report also require a 3PL\norganization key, and return **403** otherwise. The SKU and inventory\ntransaction endpoints and the month-end and Sad Cow reports return **403**\nto a key whose organization has no fulfillment vendor.\n\n**Coming:** create, rotate and revoke your own keys under **API keys**.\nA key manages the keys of its own organization and of no other, so an\nintegration can rotate itself with nobody in the loop; an organization\nadmin signed in to the partner portal can do the same by hand. Two guards\nstop a key locking you out of your own account: a key cannot revoke itself\nwhile it is the only active key on the organization, and rotating your own\nkey always leaves it working for the overlap window. Until they ship, your\nCrowd Cow contact issues and revokes keys for you.\n\n## Errors\n\nEvery failure is [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457.html)\n`application/problem+json`, one shape for every endpoint and status.\n\n```json\n{\n  \"type\": \"/problems/validation-error\",\n  \"title\": \"Request is not valid.\",\n  \"status\": 400,\n  \"detail\": \"status must be one of: not_packed, packed, shipped, cancelled\",\n  \"instance\": \"/requests/7f1c3e2a-…\"\n}\n```\n\nBranch on `type`, not `detail`: `detail` is written for a person and may be\nreworded without notice. `instance` is the request id; quote it to us to\ntrace a request.\n\n| `type` | Status | When |\n|---|---|---|\n| `/problems/unauthorized` | 401 | No key, or a revoked one. |\n| `/problems/forbidden` | 403 | Your key cannot reach this endpoint or this organization. |\n| `/problems/not-found` | 404 | No such record on your account. |\n| `/problems/validation-error` | 400 / 422 | A bad parameter (400) or a bad request body (422). |\n| `/problems/idempotency-key-reused` | 422 | **Coming**, with API key management: an `Idempotency-Key` you have already used, with a different body. |\n| `/problems/policy-refused` | 422 | The change is not allowed on that order; `reasons` says why. |\n| `/problems/cursor-invalid` | 400 | A cursor we did not issue, or the filters changed mid-walk. |\n| `/problems/quota-exceeded` | 429 | You are over the rate limit; `Retry-After` says how long to wait. |\n| `/problems/no-rate-card` | 422 | A quote asked of an organization with no rate card with us. |\n| `/problems/rate-unavailable` | 503 | No shipping option could be priced right now; retry after `Retry-After`. |\n| `/problems/unpriceable` | 422 | No shipping option could be priced, and retrying will not change that. |\n\nTwo optional members carry the specifics: `errors` on a 422 from\n`PUT /orders` or `PUT /skus` (`[{ \"code\": \"invalid\", \"detail\": \"…\" }]`), and\n`reasons` on a policy refusal.\n\n**Keys that existed before 10 September 2026 keep the older bodies.** On\nsuch a key, nothing above applies: `PUT /orders` and `PUT /skus` are\n`{ \"errors\": [\"...\", \"...\"] }`, everything else is `{ \"message\": \"...\" }`,\nand the media type is `application/json`. Status codes are unchanged. Ask\nyour Crowd Cow contact to move your key to the modern bodies, or send\n`CrowdCow-Response-Format: current` on a request to get the shapes this\ndocument describes on that request alone.\n\n**Rotating your key does not change your shapes.** A replacement key issued\nfor an organization that holds one of these keeps the older bodies.\n\n**Two exceptions:** a **401** and a **429** always use the problem document,\nwhatever your key.\n\n## Fulfillment centers\n\nThe `fc` field, in requests and responses, uses these codes.\n\n| Code | Location |\n|---|---|\n| `dunmore` | Scranton, PA |\n| `dallas` | Dallas, TX |\n| `watsonville` | Watsonville, CA |\n\n## Lists and paging\n\nEvery list returns the same envelope.\n\n```json\n{ \"data\": [ … ], \"has_more\": true, \"next_cursor\": \"eyJ2Ijoi…\" }\n```\n\nFollow `next_cursor` until it comes back `null`. The same link is in a\n`Link: \u003c…\u003e; rel=\"next\"` header. `limit` defaults to 50, maximum 200; a\nlarger value is clamped.\n\nThe cursor is opaque; do not construct or parse one. It carries the filters\nit was issued under, so changing one part-way through a walk is a\n`400 /problems/cursor-invalid`; restart from the first page.\n\nThere is **no total count**, in the body or in a header. A walk visits every\nrow exactly once, even while rows are being written underneath it.\n\n## Rate limits\n\n**600 requests per minute per API key**, counted over a fixed 60-second\nwindow. Reads and writes share the one budget.\n\n**Heavy reads have their own budget.** Every `/reports/*` path, every path\nunder an invoice (`/invoices/{id}/…`) and every `/summary` sibling is also\ncounted under a second, lower per-key limit of **60 requests per minute**,\nso a loop over reports cannot crowd out order lookups. It is reported in\nthe same headers, under the policy name `per-key-reports`.\n\n**`POST /quote` and `GET /orders/{id}/rescheduling_options` have their own\nlimits too**, because both ask the carriers for live rates. Together they\nare counted at **30 per minute** per key (`per-key-quote`), no more than\n**5 in any 10 seconds** per key (`per-key-quote-burst`), and **16 in any\n10 seconds** across all partners (`quote-total`). No more than **5** of\nthem run at once across all partners; a sixth is answered `429` with\n`Retry-After: 1`. Spread quotes out rather than sending a batch at once.\n\nTwo limits apply per IP address as well: **1,200 per minute** over every\nrequest to the API, with or without a key, and **60 per minute** over the\nrequests that carry **no** API key. This document is the exception: it is\npublic and counts against that 60, so fetch it when your client is built,\nnot on a loop.\n\nEvery response, successful or not, carries the IETF\n[RateLimit](https://www.ietf.org/archive/id/draft-ietf-httpapi-ratelimit-headers-11.html)\nfields:\n\n```\nRateLimit-Policy: \"per-key\";q=600;w=60\nRateLimit: \"per-key\";r=597;t=42\n```\n\n`q` is the quota and `w` the window in seconds; `r` is how many requests you\nhave left and `t` how many seconds until the window resets. Most requests\nare counted by more than one limit; the headers report whichever is closest\nto running out, so the policy named is `\"per-key\"`, `\"per-key-reports\"`,\n`\"per-key-quote\"`, `\"per-key-quote-burst\"`, `\"quote-total\"`, `\"per-ip\"` or\n`\"per-ip-total\"`. There are no `X-RateLimit-*` headers.\n\nOver the limit you get **429** with `Retry-After` in whole seconds and an\n`application/problem+json` body, whatever your key. Wait `Retry-After`\nseconds and retry; nothing was processed, so a retry is safe for every\nendpoint.\n\n## What the API cannot do\n\nEach of these is available in the portal.\n\n- **Scan history** — a shipment carries its latest carrier scan only.\n- **Lots** — the lot list and each lot's detail.\n- **Transfers** — moving your stock between our fulfillment centers.\n- **SKU archiving**, internal notes, images, weeks-of-stock and cost figures.\n- **Packs** — you can use a pack code in an order's items, but creating and\n  editing packs is portal-only.\n- **Users** — account management is portal-only.\n\nIf one of these blocks an integration you are building, email\n[fulfillment@crowdcow.com](mailto:fulfillment@crowdcow.com).\n\n## Connect an AI assistant\n\nConnect Claude, ChatGPT, Claude Code or Codex to your account and ask about\nyour orders, shipments, stock and invoices in plain English, or have it\nreschedule, cancel and update orders for you. It can do what the API does\ntoday, for one organization.\n\n```\nhttps://3pl.crowdcow.com/mcp\n```\n\nAdd that URL to your assistant, then sign in with your partner portal login\nand choose the organization. The assistant acts as you: it sees what you\nsee in the portal, its changes are recorded under your name, and it stops\nworking if you lose access to the organization. Most assistants ask before\nthey make a change; check your assistant's permission settings to be sure.\n\n**Claude**: on the web or desktop app, **Customize \u003e Connectors**, then\n**+ Add \u003e Add custom connector**. Name it, paste the URL and sign in. It\nthen works in the mobile app too. On a Team or Enterprise plan an owner\nadds it under **Organization settings \u003e Connectors \u003e Add \u003e Custom \u003e Web**,\nand each person clicks **Connect**.\n\n**ChatGPT**: open [chatgpt.com/plugins](https://chatgpt.com/plugins), click\n**+**, then **Add custom MCP server**. Name it, paste the URL, choose\n**OAuth**, confirm the warning and click **Create as a plugin**. Install\nthe plugin, then type `@` in a chat to use it.\n\n**Claude Code**: run this, then `/mcp` in Claude Code to sign in.\n\n```\nclaude mcp add --transport http --scope user \\\n  crowdcow https://3pl.crowdcow.com/mcp\n```\n\n**Codex CLI**:\n\n```\ncodex mcp add crowdcow --url https://3pl.crowdcow.com/mcp\ncodex mcp login crowdcow\n```\n\n## For AI agents\n\nThis document is public and needs no API key. Fetch it at\n`https://www.crowdcow.com/api/fulfillment/v1/openapi.json` and read it\nbefore writing integration code: it carries every endpoint, parameter, enum,\nstatus code and response schema, plus the behaviour above. Worked examples\nlive alongside the operations.\n","contact":{"name":"Crowd Cow","url":"https://3pl.crowdcow.com/api-reference"}},"servers":[{"url":"https://www.crowdcow.com/api/fulfillment/v1","description":"Production"}],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"PartnerOAuth":["partner_mcp"]}],"tags":[{"name":"SKUs","description":"Keyed on `sku_id`, which is your own identifier and what an order's\n`items` reference. Every other field is overwritten by what you send, so\nsend the whole record each time — an omitted `barcode` clears the stored\none.\n\n`weight` is pounds; `width`, `length` and `height` are inches. Inventory\ncomes back per fulfillment center: `on_hand_quantity` is physical stock,\n`reserved_quantity` is committed to orders not yet shipped, and\n`available_quantity` is what you can still sell. Quote against\n`available_quantity`.\n"},{"name":"Orders","description":"Keyed on `order_id`. One order per request; there is no batch endpoint.\nAn update to a packed, shipped or cancelled order is refused with `422`\nand a reason; `can_modify` on any order response tells you in advance.\n\n**`shipping_method` has four meanings.** Omitted — any existing override\nis left alone. `shipping_option_name` set — ship on that quoted option\n(below). `carrier` + `service` set — pin the order to that service.\nBoth explicitly null — clear the override and return to automatic routing.\n\n**`shipping_option_name` picks the option.** Send a quoted option's\n`shipping_option_name` with its `delivery_date` as `preferred_arrival_date`\nand the order ships on exactly that option that day; without a date, on\nthat option's soonest date. If the option can no longer make it, the order\nis routed as if you had not named one. The order response's\n`shipping_method` shows where it landed. The order remembers the option:\nan update that leaves `shipping_method` out stays on it, on its current\nday unless you send a new date. A `carrier` + `service` pin or explicit\nnulls let it go, and so does an option that can no longer reach a new\ndate or address. Rescheduling with `shipping_option_name` chooses the\noption the same way.\n\n**`preferred_arrival_date` picks the slot.** Crowd Cow picks the plan\narriving closest to your date, and the cheapest service among those\narriving that day — an air service when ground cannot make it. To land on\na quoted option, send its `delivery_date`. A date in the past is ignored.\n\n**The ship-to state must agree with the ship-to ZIP.** A mismatch fails\nthe whole request with `422` and creates no order; resend once corrected.\nUnknown and non-US ZIPs pass. The city is not checked.\n\nOn the order object, `items` excludes Crowd Cow packing inserts — your own\nproducts only, at the quantity ordered, not the quantity picked — and\n`shipments` lists shipped shipments only, so an order that has not shipped\nhas an empty array.\n"},{"name":"Shipments","description":"Tracking, delivery state and exception flags for the boxes we ship for you.\nThe list covers shipments that have been labeled or shipped; an order not yet\npicked has no shipment row.\n\n`status` is `not_shipped`, `in_transit`, `out_for_delivery` or `delivered`.\n`last_scan` carries the most recent carrier scan; scan history is not exposed.\n`write_off` is `null`, `lost` or `destroyed_in_transit`, and a written-off box\nreports `days_late` of 0.\n\n`/shipments/summary` takes the same filters and returns the counts and carrier\nvocabulary a list header needs, so a page of rows never pays for them.\n"},{"name":"Delivery feedback","description":"The \"How did your box arrive?\" reports your customers send from your branded\nfeedback form: how the box, liner and dry ice looked, whether the food was\nstill frozen, any damage, a 0–10 score and their photos. The same reports\nyour portal's Delivery Feedback page lists.\n"},{"name":"Inventory transactions","description":"Every movement of your stock in our warehouses: receipts, picks, cycle-count\ncorrections and write-offs.\n\n`quantity` is signed — receipts and upward corrections positive, picks and\nwrite-offs negative. `box_id`, `order_id` and `lot` are present when the movement\nhas one. `reason` is `delivery` (cases you sent us, put into stock),\n`receiving` (a correction to a receipt), `pick`, `cycle_count` or `sad_cow`,\nor for any other movement its own name (`fc_transfer`, `pack_order`,\n`b2b_order`, `lost`, `initial`). Every one of them works as a `reason` filter. Only our\ninternal prepacking step and zero-quantity rows are excluded.\n\n`/inventory_transactions/summary` totals the same filtered set per SKU.\n"},{"name":"Invoices","description":"Your issued invoices, their charges and their documents. Only issued invoices\nare returned; drafts and voided invoices do not appear.\n\n`id` is the invoice number printed on the document, and the identifier every\ninvoice path takes. `status` is `sent` or `exported`. Money is a decimal string\nin dollars with its currency beside it, never cents. Sorted newest billing\nperiod first; `limit` and `cursor` work as they do on orders.\n\nShipment costs are their own paginated list rather than being inlined: the\nlargest invoice bills about 13,000 shipments.\n"},{"name":"Reports","description":"Three finished reports. One request returns the whole report: no cursor, no\nsummary sibling.\n\nMonth-end inventory counts units you own on hand at each fulfillment center at\nthe close of a month; `month` is `YYYY-MM` and defaults to the last completed\none. Sad Cow reports units written off, by SKU, center and reason. Delivery\nperformance returns the on-time rate, average transit and alert counts for a\nperiod, each against the prior one.\n"},{"name":"Quoting and dates","description":"An inventory-aware shipping quote for a cart — what each shipping option\nwould cost you and when it would arrive — and the delivery dates\navailable to a destination. Both are 3PL organization keys only.\n\nThe quote lists each shipment's options for up to a week of delivery\ndates, each priced at your contracted rates, itemised, and tagged\n`recommended`, `cheapest` or `fastest`. An AxleHire (Jitsu) option's\ntransportation is an **estimate** from what comparable shipments were\nbilled, because AxleHire gives no price before it invoices.\n\n- **`shipments`** — one entry per fulfillment center used, each with its\n  own `options`. `fc.code` is the internal code; `fc.display` is a\n  customer-safe string that never names the warehouse. A `reason` appears\n  only when nothing could arrive in your date range, or this is a\n  secondary shipment because the primary center was short.\n- **`unfulfillable`** — present only when something cannot be quoted. Each\n  entry carries `sku_id`, `quantity_requested`,\n  `quantity_available_across_all_fcs` and a reason:\n  `no_fc_has_inventory`, `insufficient_inventory_across_all_fcs`,\n  `no_shipping_options_available` or `no_eligible_shipment_plan`.\n- **`ground_eligibility`** — `eligible` is true only when every item ships\n  and every shipment is a ground service. `blockers` lists what the\n  nearest ground center is short on, each with a\n  `recommended_max_quantity` — reduce to that to keep ground. `eligible`\n  false with empty `blockers` means air was quoted for speed, not for lack\n  of stock.\n\nA cart whose items are all out of stock still returns **200** with an\nempty `shipments` array, so read `shipments`, `unfulfillable` and\n`advisory` rather than the status code. An **empty** `items` array is a\n`422`.\n\nOn the calendar, `weeks` defaults to 4 and is clamped to 1–12; a\nnon-integer is a `422`.\n"},{"name":"API keys","description":"**Coming.** Create, rotate and revoke the keys your integrations\nauthenticate with, without emailing anyone.\n\nA token is shown **once**, in the response that creates it. We store only a\nhash, so a lost token cannot be recovered — issue another key and revoke\nthe old one. Everywhere else a key is identified by `id`, `name` and\n`prefix`, the first eight characters of its token: enough to match a key to\na log line, not enough to use it.\n\nRotation is create-then-retire. `rotate` issues the replacement\nimmediately and schedules the old key's revocation for the end of an\noverlap window you choose, so you can deploy the new token before the old\none stops working. `last_used_at` tells you whether anything is still\ncalling with it — a key with nothing on it for thirty days is safe to\nrevoke early. Revocation takes effect at once and cannot be undone.\n\nEither credential reaches all four operations. A key manages the keys of\nits own organization, and an organization admin signed in to the partner\nportal manages the same keys with `CrowdCow-Organization` naming which\norganization they are acting for. A key may rotate itself — the successor\nis issued and the calling key keeps working for the overlap window — but\nit cannot revoke itself while it is the only active key you have.\n\nCreating and rotating are the only operations on this API that are not\nidempotent, so both require an `Idempotency-Key`. A retry after a timeout\nreturns the original response, token included, rather than a second key.\n\n`last_used_at`, `expires_at` and `revoked_at` appear only once they have a\nvalue.\n\nThese four operations are the only ones an API key cannot call. They need\nan organization admin signed in to the partner portal — the same gate as\nthe Users page — and answer **403** to any key, in either header.\n"},{"name":"Meta","description":"This document. Public, and the only endpoint needing no API key."},{"name":"Changelog","x-traitTag":true,"description":"Newest first, dated when the change went live. Changes are additive: a new\nendpoint, field, parameter or value never removes one you already read,\nand an entry says so when a value you read is renamed. Anything marked\n**Coming** in this reference is not in this list until it ships. Link to\nan entry by its date, e.g. `#changelog-2026-11-03`.\n\n\u003ch2 id=\"changelog-2026-11-06\"\u003e6 November 2026\u003c/h2\u003e\n\n- Every endpoint documents a `CrowdCow-Act-As` header. Crowd Cow's own\n  assistant uses it to answer you in Slack; a request from your key that\n  sends it is refused with `403`, so leave it out.\n\n- New: change one order's shipping address without resending the whole\n  order. `POST /orders/{id}/shipping_address_options` lists the delivery\n  dates the order could ship on to the new address, and `PATCH\n  /orders/{id}/shipping_address` saves the address on the option you\n  pick. Send only the address fields that change.\n- New: `PATCH /orders/{id}/gift_message` changes the message on an\n  order's gift card, or removes the card when sent empty.\n\n\u003ch2 id=\"changelog-2026-11-04\"\u003e4 November 2026\u003c/h2\u003e\n\n- UPS Ground is one shipping option, `UPS Ground`, wherever an option is\n  named: quotes, the delivery calendar and an order's\n  `shipping_option_name`. It replaces `UPS 1 day Ground` to\n  `UPS 4 day Ground`; the delivery date still depends on the address.\n  Sending one of the old names still works and means `UPS Ground`.\n\n\u003ch2 id=\"changelog-2026-11-03\"\u003e3 November 2026\u003c/h2\u003e\n\n- `carrier` names Jitsu `Jitsu` everywhere it appears: quotes, orders,\n  shipments, rescheduling options, invoice shipment lines and the\n  `carriers` list on `GET /shipments/summary`. It was `AxlehireV3`. The\n  `carrier` filter on `GET /shipments` takes either name.\n- FedEx ground is always named `FedEx Ground` in `shipping_option_name`, on\n  quotes, orders and rescheduling options, whether the box goes to a home or\n  a business. Pick it by that name. `service` reports the code FedEx ships:\n  `GROUND_HOME_DELIVERY` to a home, `FEDEX_GROUND` to a business. Quotes\n  issued earlier on 3 November named it `Fedex Home Delivery`, and orders\n  naming it still ship on FedEx ground.\n\n\u003ch2 id=\"changelog-2026-10-30\"\u003e30 October 2026\u003c/h2\u003e\n\n- `GET /inventory_transactions` and its `/summary`: a new `reason`, `lost`,\n  for units picked for an order that was then cancelled and that we could\n  not find to put back in stock. It comes with a `pick` of the same units\n  reversing the original pick, so the two net to the units written off.\n  `lost` works as a `reason` filter and counts in `total`.\n- `packaging.dry_ice_in_pounds` on an order now includes the extra ice you\n  add to every order, so it is the dry ice we plan to pack in each box.\n- Each shipment, on `GET /shipments` and in every order's `shipments`, adds\n  `dry_ice_packed_lbs`: the dry ice our packer weighed into that box.\n- Invoice shipment costs (`GET /invoices/{id}/shipment_costs`) add\n  `gel_packs`, `liner` and `printing`, so the rows add up to the invoice\n  Summary. Gel packs and liners bill once per order, on its first box.\n- Invoices can carry two new line `category` values for a wholesale\n  order your buyer collects from our warehouse: `b2b_case` (each case\n  that left on the truck) and `b2b_pallet` (the pallet and its supplies).\n- Invoice shipment rows report each box's `shipment_weight` as our scale\n  weighed it when it was packed, where before they reported the planned\n  weight, and `product_weight` is that weight less the box and the dry\n  ice packed, so the row adds up. Invoices sent earlier show these weights\n  too when you read them again; their charges don't change.\n\n\u003ch2 id=\"changelog-2026-10-27\"\u003e27 October 2026\u003c/h2\u003e\n\n- Each shipment now lists what is in that box: `items` on `GET /shipments`\n  and on every order's `shipments`, with the SKU, its name and how many\n  units, inserts and notes included. It is\n  `null` when we can't say which box holds what.\n\n\u003ch2 id=\"changelog-2026-10-23\"\u003e23 October 2026\u003c/h2\u003e\n\n- New: `GET /delivery_feedback` lists the \"How did your box arrive?\" reports\n  your customers sent, with their answers, comments and photos, and a\n  `problems` filter for the boxes that arrived damaged, soft or thawed.\n- `GET /shipment_calendar` and `POST /quote` refuse a ZIP code that does\n  not exist with a `422` naming it, instead of offering dates to it.\n- `GET /inventory_transactions`: the descriptions now say what `delivery`\n  has always meant, cases arriving at our warehouse and going into stock,\n  and that `receiving` is a correction to a receipt. The data is unchanged.\n- Connect your AI assistant to `https://3pl.crowdcow.com/mcp`: one of your\n  people signs in with their portal login, picks your organization, and\n  the assistant acts as them (`PartnerOAuth`). Setup for Claude, ChatGPT,\n  Claude Code and Codex is under **Connect an AI assistant**.\n- `GET /orders/{id}/rescheduling_options` now defaults to options\n  arriving in the next three weeks; pass `arrival_date_max` for later\n  dates.\n- A key that still gets the older bodies and bare arrays can send\n  `CrowdCow-Response-Format: current` to get the shapes this document\n  describes, `next_cursor` included, on that request.\n- `GET /orders/{id}/rescheduling_options`: `arrival_date_min` and\n  `arrival_date_max` list only the options arriving in that window, so you\n  can ask about the dates you are weighing rather than three months of\n  every service.\n- `GET /inventory_transactions` and its `/summary`: the `reason` filter\n  takes every reason a row can carry, including `fc_transfer`,\n  `pack_order`, `b2b_order` and `initial`.\n- `GET /orders/{id}/rescheduling_options`: a plan with no `cost` now says\n  why in `cost_unavailable_reason`: `rate_unavailable` (the carrier could\n  not be reached just then; asking again may price it), `unpriceable`\n  (asking again will not help) or `no_rate_card`.\n- `GET /orders/{id}/rescheduling_options`: the dry ice in each `cost` is\n  what your order will be packed with on that plan, the same figure the\n  order shows once it moves there, rather than an estimate from your recent\n  orders.\n- A cancelled order can't be changed: `can_modify` is `false`, and\n  `PUT /orders`, the overrides, `GET /orders/{id}/rescheduling_options`\n  and `POST /orders/{id}/reschedule` answer `422` with the reason\n  `Order already cancelled.` Re-sending a cancelled order no longer\n  reserves stock for it.\n- A cancelled order still lists its `items`, as they were when it was\n  cancelled. Orders cancelled before today list none.\n- `POST /orders/{id}/reschedule`: an `arrival_date` that is not among the\n  order's options is refused with a reason saying so, apart from a date\n  that is offered by another shipping option, which names the options it\n  has.\n\n\u003ch2 id=\"changelog-2026-10-19\"\u003e19 October 2026\u003c/h2\u003e\n\n- `PUT /orders`: send a quoted option's `shipping_option_name` in\n  `shipping_method` and the order ships on exactly that option, on your\n  `preferred_arrival_date` or its soonest date. Line-haul options, which\n  have no `service`, can now be chosen this way.\n- `PUT /orders` and `POST /orders/{id}/reschedule`: the order remembers the\n  option you chose by `shipping_option_name`, so a later update that leaves\n  `shipping_method` out keeps it.\n\n\u003ch2 id=\"changelog-2026-10-13\"\u003e13 October 2026\u003c/h2\u003e\n\n- `PUT /orders`: a new order matching one of your order screening rules is\n  refused with `422`, and the error names the rule. You write the rules in\n  plain English in your Crowd Cow portal.\n\n\u003ch2 id=\"changelog-2026-10-08\"\u003e8 October 2026\u003c/h2\u003e\n\n- `POST /quote` and invoices: the truck fee line reads `Line haul`\n  (was `Line haul — same-day` / `Line haul — next-day`). It is one fee per\n  shipping route, the same for every partner; `category` is still\n  `line_haul`.\n\n\u003ch2 id=\"changelog-2026-09-24\"\u003e24 September 2026\u003c/h2\u003e\n\n- `POST /quote` changed shape, the one exception to additive changes,\n  made before any integration relied on the old one. It returns each\n  shipment's shipping options as a list:\n  `shipments[].options`, one per shipping lane and delivery date, each\n  with its dates, `tags` (`recommended`, `cheapest`, `fastest`) and\n  `cost`. Ask for a week of delivery dates with `delivery_date_min` and\n  `delivery_date_max`, and for every option with `all_options: true`;\n  without it you get the recommended option alone. This replaces\n  `requested_delivery_date`, `optimize_for`, `include_alternatives`,\n  `rate`, `alternatives` and the shipment's own dates and `cost`, which\n  are refused or gone.\n- Every option has a price. An option we cannot price is left out; if no\n  option for a shipment can be priced, the quote is a `503`\n  `/problems/rate-unavailable` to retry when a carrier could not be\n  reached, or a `422` `/problems/unpriceable` when retrying will not help.\n  An organization with no rate card gets `422` `/problems/no-rate-card`\n  from `POST /quote`, rather than a quote with no cost.\n- An item whose stock sits only at a fulfillment center that cannot ship\n  to the destination is `unfulfillable` with reason\n  `no_eligible_shipment_plan`, not `insufficient_inventory_across_all_fcs`,\n  which now means the centers that can ship there hold too few.\n- `recommended` is the option an order is routed to: the cheapest\n  shipping tier (ground before 2nd Day Air before Next Day Air), then the\n  soonest. A delivery date in the past is a `422`.\n- `GET /orders/{id}/rescheduling_options` always lists the dates an order\n  can move to; `cost` is left off an option we cannot price, rather than\n  the request failing.\n- Quote and rescheduling costs now include packaging, dry ice and the\n  shipping liner, estimated from your cart and destination and priced at\n  your contracted rates (`basis: estimate`; line categories `packaging`,\n  `coolant_dry_ice` and `liner`). `excludes` is now usually empty: it\n  lists only a box or dry ice we have no rate entered for.\n- The `packaging` estimate prices the box you ship almost every order in\n  (at least 90% of your last 30 days' boxes) when it holds the cart, and\n  otherwise the smallest box that holds it.\n\n- AxleHire (Jitsu) estimates (`basis: historical_estimate`) now use your\n  destination's delivery market and the cart's weight, the same for\n  every partner. A market with no recent AxleHire history uses the median\n  across all markets, so every AxleHire option now has a price.\n\n\u003ch2 id=\"changelog-2026-09-23\"\u003e23 September 2026\u003c/h2\u003e\n\n- `POST /quote` returns what each shipping option costs you: `cost` on each\n  shipment and on the quote, itemised the way your invoice is. New\n  request fields `optimize_for` and `include_alternatives`; new response\n  fields `alternatives` and `rate.cheapest`. AxleHire transportation is an\n  estimate (`basis: historical_estimate`). A shipment on your own carrier\n  account shows that carrier's rate on your account\n  (`basis: your_carrier_account`), which you pay the carrier directly.\n- `GET /orders/{id}/rescheduling_options` returns `current`, the plan the\n  order holds now, and a `cost` on it and on every option.\n- `GET /skus/{sku_id}`: one SKU, with what is reserved against it and what\n  is packed but not yet shipped.\n- `POST /quote` and `GET /orders/{id}/rescheduling_options` share their own\n  limits: 30 per minute and 5 per 10 seconds per key, 16 per 10 seconds\n  and 5 at once across all partners, separately from the 600 shared by\n  everything else.\n\n\u003ch2 id=\"changelog-2026-09-15\"\u003e15 September 2026\u003c/h2\u003e\n\n- `GET /shipments`, `GET /shipments/summary` and\n  `GET /reports/delivery_performance`.\n\n\u003ch2 id=\"changelog-2026-09-14\"\u003e14 September 2026\u003c/h2\u003e\n\n- `GET /inventory_transactions` and its `/summary`, the month-end inventory\n  and Sad Cow reports, and invoices: list, detail, per-order and\n  per-shipment costs, PDF and spreadsheet.\n\n\u003ch2 id=\"changelog-2026-09-11\"\u003e11 September 2026\u003c/h2\u003e\n\n- `PUT /orders` accepts an order without a billing address.\n\n\u003ch2 id=\"changelog-2026-09-10\"\u003e10 September 2026\u003c/h2\u003e\n\n- One list envelope with cursor paging, and `application/problem+json`\n  errors on every endpoint. Keys that existed before this date keep the\n  older bodies (see Errors).\n\n\u003ch2 id=\"changelog-2026-09-09\"\u003e9 September 2026\u003c/h2\u003e\n\n- Rate limits, with `RateLimit` and `RateLimit-Policy` headers and `429`\n  with `Retry-After`.\n"}],"paths":{"/skus":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["SKUs"],"operationId":"listSkus","summary":"List your SKUs, with inventory per fulfillment center","x-mcp-description":"Use to see the partner's SKUs and the units of each at every fulfillment center.\n`inventory` is keyed by FC code (`dunmore` is Scranton, PA): `on_hand_quantity`\nis in the building, `reserved_quantity` is held for open orders, and\n`available_quantity` is on hand less reserved. Archived SKUs are included. For\none SKU, or what the partner's own orders have committed against it, use\nget_sku. For more rows follow `next_cursor` until it is null, rather than\nraising `limit`.\n","description":"Your SKUs in pages of 50 — including archived ones, until the filter\nbelow lands. `limit` and `cursor` work as they do on orders.\n\n**If your key existed before 10 September 2026** this endpoint returns\nevery SKU as a bare JSON array, unpaged — there is no envelope, no\ncursor and no `Link` header, and `limit` is ignored rather than\nrefused. Send `CrowdCow-Response-Format: current` to get pages instead.\n","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"archived","in":"query","x-status":"coming","description":"Coming: archived SKUs will be excluded by default. `include` returns\nthem alongside the rest; `only` returns just them.\n","schema":{"type":"string","enum":["include","only"]}}],"responses":{"200":{"description":"A page of SKUs.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Link":{"$ref":"#/components/headers/LinkNext"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SkuList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"tags":["SKUs"],"operationId":"upsertSku","summary":"Create or update a SKU","x-mcp-description":"Changes data. Use to create a SKU or change one, keyed on the partner's\n`sku_id`. Every field you leave out is overwritten with nothing (an omitted\n`barcode` clears the stored one), so first read the SKU with get_sku and send\nthe whole record back with your change. To undo, send the previous record again.\n","description":"Keyed on `sku_id`. Every other field is overwritten by what you send,\nso send the whole record each time — an omitted `barcode` clears the\nstored one.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SkuUpsertRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/SkuUpsertRequest"}}}},"responses":{"200":{"description":"The stored SKU.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sku"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableErrors"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/orders":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Orders"],"operationId":"searchOrders","summary":"Fetch orders","x-mcp-description":"Use to find orders: by the partner's `id` (exact; an empty `data` means no\nmatch), by free text `q` (order ids, recipient, SKU, external reference), by\nscheduled ship date (`ship_date_min` / `ship_date_max`, YYYY-MM-DD) or by\n`status` (`not_packed`, `packed`, `shipped`, `cancelled`). Each order has its\nitems, ship-to address, shipping method, scheduled ship date, `can_modify`, and\nany shipments with tracking. For a box's delivery state use list_shipments. For\nmore rows follow `next_cursor` until it is null, rather than raising `limit`.\n","description":"An `id` lookup returns every order carrying that identifier: normally\none, an empty `data` if none match, more than one if you have reused an\nidentifier. There is no `404` here.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/orders?id=1234567' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\nAll filters are optional and all combine. A value a filter cannot read\nis refused with a `400` naming the parameter.\n\n```bash\n# Everything shipping in a date window, oldest first\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/orders?ship_date_min=2026-06-01\u0026ship_date_max=2026-06-30\u0026sort=ship_date' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n\n# Anything mentioning a SKU, a recipient or one of your external references\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/orders?q=s_ribeye_12oz' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n\n# Cancelled orders only, 100 at a time\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/orders?status=cancelled\u0026limit=100' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\nFollow `next_cursor` until it comes back `null`:\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/orders?limit=200' …\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/orders?limit=200\u0026cursor=eyJ2Ijoi…' …\n```\n\n**If your key existed before 10 September 2026** this endpoint still\nreturns a bare JSON array, and `page` / `per_page` / the `X-Total-*`\nheaders are gone. Pages are still `limit` rows, and the cursor cannot\ntravel in a bare array, so follow the `Link: rel=\"next\"` header, or send\n`CrowdCow-Response-Format: current` to get the envelope with\n`next_cursor`.\n","parameters":[{"name":"id","in":"query","description":"Exact match on your `order_id`.","schema":{"type":"string"}},{"name":"q","in":"query","description":"Free text across your order id, your customer order id, our order\nnumber, the order token, the recipient name, SKU name, SKU id, and\nany `external_references` value.\n","schema":{"type":"string"}},{"name":"ship_date_min","in":"query","description":"`YYYY-MM-DD`, inclusive, on the scheduled ship date. A date we\ncannot read is a `400`, never a dropped filter.\n","schema":{"type":"string"}},{"name":"ship_date_max","in":"query","description":"`YYYY-MM-DD`, inclusive, on the scheduled ship date.","schema":{"type":"string"}},{"name":"start_date","in":"query","description":"The older spelling of `ship_date_min`, still accepted.","schema":{"type":"string"}},{"name":"end_date","in":"query","description":"The older spelling of `ship_date_max`, still accepted.","schema":{"type":"string"}},{"name":"status","in":"query","description":"Mutually exclusive. An unrecognized value is a `400` naming the four.\n","schema":{"type":"string","enum":["not_packed","packed","shipped","cancelled"]}},{"name":"sort","in":"query","description":"`-` for descending. An unrecognized value is a `400`.","schema":{"type":"string","enum":["ship_date","-ship_date"],"default":"-ship_date"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"updated_at_min","in":"query","x-status":"coming","description":"Coming: RFC 3339. Returns orders changed at or after this instant,\nand switches the default sort to `updated_at` ascending so a polling\nloop cannot miss a row. This is how you ask \"what changed since I\nlast looked\".\n","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of matching orders, newest ship date first.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Link":{"$ref":"#/components/headers/LinkNext"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"put":{"tags":["Orders"],"operationId":"upsertOrder","summary":"Create or update an order","x-mcp-description":"Changes data. Use to create an order or change one, keyed on the partner's\n`order_id`. Every call sends `order_id`, `items` and `shipping_address`, and\nthose replace what is stored, so send the full item list and address even to\nchange something else. Leave out `shipping_method`, `preferred_arrival_date` and\n`packaging` unless you mean to change them: left out, the order keeps its date,\nservice and our dry ice estimate. Do not copy them from an order you read: the\norder has no arrival date, so a copied `shipping_method` moves it to the soonest\ndate, and a copied `packaging` pins the ice. To move an order's date, use\nreschedule_order. A packed or shipped order is refused with a 422 (`can_modify`\ntells you in advance). Confirm with the person before creating an order.\n","description":"Keyed on `order_id`. One order per request; there is no batch endpoint.\nAn update to a packed or shipped order is refused with `422` and a\nreason; `can_modify` on any order response tells you in advance.\nA new order that matches one of your order screening rules (set in\nyour Crowd Cow portal under Order Screening) is refused with `422`, and\nthe error names the rule. Updates to an order we already accepted are\nnot screened.\n\n```bash\ncurl --request PUT 'https://www.crowdcow.com/api/fulfillment/v1/orders' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e' \\\n  --header 'Content-Type: application/json' \\\n  --data '{\n    \"order_id\": \"1234567\",\n    \"customer_order_id\": \"456\",\n    \"preferred_arrival_date\": \"2026-10-23\",\n    \"shipping_method\": { \"shipping_option_name\": \"LAX LH UPS\" },\n    \"packaging\": { \"dry_ice_in_pounds\": 10 },\n    \"items\": [{ \"sku\": \"s_ribeye_12oz\", \"quantity\": 2 }],\n    \"shipping_address\": {\n      \"name\": \"John Doe\",\n      \"address_1\": \"123 Main St\", \"address_2\": \"Apt 1\",\n      \"city\": \"Kirkland\", \"state\": \"WA\", \"postal_code\": \"98033\",\n      \"phone_number\": \"1234567890\",\n      \"delivery_instructions\": \"Gate code 4432\"\n    },\n    \"billing_address\": {\n      \"address_1\": \"123 Billing St\", \"city\": \"Seattle\",\n      \"state\": \"WA\", \"postal_code\": \"98104\"\n    }\n  }'\n```\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderUpsertRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/OrderUpsertRequest"}}}},"responses":{"200":{"description":"The stored order.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableErrors"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/orders/{id}/cancel":{"parameters":[{"$ref":"#/components/parameters/ActAs"},{"$ref":"#/components/parameters/OrderId"}],"post":{"tags":["Orders"],"operationId":"cancelOrder","summary":"Cancel an order","x-mcp-description":"Changes data, and cannot be undone: there is no way to un-cancel an order\nthrough this API. Confirm with the person first. Once an order is packed or\nshipped it can no longer be cancelled, and the answer is a 422 with the reason.\nCancelling an order that is already cancelled changes nothing and returns it.\n","description":"Cancelling an already-cancelled order is a no-op that returns `200` —\nsafe to retry. An order too far along returns `422` naming the reason.\n","responses":{"200":{"description":"The cancelled order.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableMessage"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/orders/{id}/override_ice":{"parameters":[{"$ref":"#/components/parameters/ActAs"},{"$ref":"#/components/parameters/OrderId"}],"patch":{"tags":["Orders"],"operationId":"overrideOrderIce","summary":"Override the dry ice weight","x-mcp-description":"Changes data. Use to set how much dry ice an order ships with, in\n`dry_ice_in_pounds` or `dry_ice_in_kilograms`, in place of our estimate. To\nundo, send the same key with an empty value, which clears the override and\nreturns the order to our estimate. Sending neither key is an error.\n","description":"Send `dry_ice_in_pounds` or `dry_ice_in_kilograms` (converted and\nrounded). Sending the key with an empty value clears the override;\nomitting **both** keys is an error, not a clear.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverrideIceRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/OverrideIceRequest"}}}},"responses":{"200":{"description":"The order, with the override applied.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableMessage"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/orders/{id}/override_shipping_method":{"parameters":[{"$ref":"#/components/parameters/ActAs"},{"$ref":"#/components/parameters/OrderId"}],"patch":{"tags":["Orders"],"operationId":"overrideOrderShippingMethod","summary":"Pin the order to a carrier and service","x-mcp-description":"Changes data. Use to pin an order to one carrier and service; send both\n`carrier` and `service`. The order is moved onto a plan with that service on its\ncurrent ship date, which can change its arrival date and cost; if none exists,\nnothing changes and the answer is a 422. To undo, send both empty, which returns\nthe order to automatic routing. To move an order to another date, use\nreschedule_order instead.\n","description":"Send **both** `carrier` and `service`, or both empty to clear the\noverride and return the order to automatic routing. One without the\nother is a `422`. The order is rescheduled onto a plan matching the new\nservice; if none exists for its current ship date the request fails\nwith `422` and nothing changes.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverrideShippingMethodRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/OverrideShippingMethodRequest"}}}},"responses":{"200":{"description":"The order, rescheduled onto the new service.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableMessage"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/orders/{id}/rescheduling_options":{"parameters":[{"$ref":"#/components/parameters/ActAs"},{"$ref":"#/components/parameters/OrderId"}],"get":{"tags":["Orders"],"operationId":"listReschedulingOptions","summary":"The dates and services this order can move to, and what each costs","x-mcp-description":"Use when someone asks which dates an order could move to, or what moving it\nwould cost. `current` is the plan the order holds now; each option is a date and\nservice it can move to. Each `cost.total` is the whole order at the partner's\ncontracted rates on that plan, additional pick charge included, so the\ndifference between an option's total and `current`'s is what the move costs or\nsaves (when both have the same `excludes`). An option without `cost` can still\nbe chosen; we could not price it. With neither `arrival_date_min` nor\n`arrival_date_max` it lists the options arriving in the next three weeks; pass\n`arrival_date_max` for later dates. An order that cannot move is a 422 with the\nreason. This tool does not move the order.\n","description":"An order that cannot be rescheduled returns `422` with the reason\nrather than an empty list.\n\n`current` carries the plan the order holds now — the same fields an\noption has, `cost` among them — and each option has a `cost` at your\ncontracted rates. Subtract the two totals to see what a different date\nis worth before you move the order.\n\nMoving an order needs no price, so every date it can move to is\nlisted. `cost` is left off a plan we cannot price, and\n`cost_unavailable_reason` says why: a carrier we could not reach just\nthen (`rate_unavailable`), a carrier with no price before it ships\n(`unpriceable`), or an organization with no rate card with us\n(`no_rate_card`).\n\nThe dry ice in a `cost` is the order's own: on `current`, what the\norder carries now; on an option, what it would carry once moved there,\nfrom the same ice chart, forecast and transit time.\n\nWithout a window you get the options arriving in the next three weeks\n(21 days, by Pacific date), which can still be a hundred options. Pass\n`arrival_date_min` and `arrival_date_max` to list only the dates you\nare weighing, or `arrival_date_max` alone to see later dates;\n`current` is returned either way.\n","parameters":[{"name":"arrival_date_min","in":"query","description":"Only options arriving on or after this date, `YYYY-MM-DD`. A\n`arrival_date_min` after `arrival_date_max` is a `400`. With\nneither date, the window is today through 21 days from today\n(Pacific); with only one, the other end is open.\n","schema":{"type":"string","format":"date"}},{"name":"arrival_date_max","in":"query","description":"Only options arriving on or before this date, `YYYY-MM-DD`. Without\nit or `arrival_date_min`, only the next 21 days are listed, so pass\nit to see later dates.\n","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"The current plan and the plans available to move to.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReschedulingOptions"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableMessage"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/orders/{id}/reschedule":{"parameters":[{"$ref":"#/components/parameters/ActAs"},{"$ref":"#/components/parameters/OrderId"}],"post":{"tags":["Orders"],"operationId":"rescheduleOrder","summary":"Move an order to one of its rescheduling options","x-mcp-description":"Changes data. Use to move an order to another delivery date or service. Call\nlist_rescheduling_options first and pass one option's `arrival_date` with its\n`shipping_option_name`; a date not among the options is refused. The move can\nchange what the order costs (compare the option's `cost.total` with\n`current`'s), so confirm with the person first. To undo, reschedule again to the\noriginal plan, if it is still offered.\n","description":"Pass an `arrival_date` from the options, plus either the\n`shipping_option_name` or a `carrier` + `service` pair.\n\nThe options are worked out again when you post, so a date or option\nthat is not among them now is a `422` that says which: a date with no\noption at all, or a date offered only by other shipping options, which\nit names.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RescheduleRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/RescheduleRequest"}}}},"responses":{"200":{"description":"The rescheduled order.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableMessage"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/orders/{id}/shipping_address_options":{"parameters":[{"$ref":"#/components/parameters/ActAs"},{"$ref":"#/components/parameters/OrderId"}],"post":{"tags":["Orders"],"operationId":"listShippingAddressOptions","summary":"The delivery dates an order could ship on to a new address","x-mcp-description":"Use before changing an order's shipping address. Send the new\n`shipping_address`; it writes nothing. Each option is a delivery date and\nservice the order can ship on to that address; `changes_fc` is true when the\norder would move to another warehouse. Show the person the options, then call\nupdate_order_shipping_address with the address and one option's\n`arrival_date`, `shipping_option_name` and `fc`. An address we cannot ship to,\nor an order too far along to change, is a 422 with the reason. It lists the\noptions arriving in the next three weeks unless you pass `arrival_date_max`.\n","description":"A `POST` so the address stays out of URLs; it changes nothing.\n`shipping_address` takes only the fields that change; the rest keep\nthe order's current values, and a field sent empty is cleared.\n\nThe options are the ones the address change can apply: once picking\nhas started, only options from the warehouse the order is at. Without\na window you get the options arriving in the next 21 days (Pacific\ndates). There is no `cost`; use `POST /quote` with the new ZIP to\ncompare prices.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShippingAddressOptionsRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/ShippingAddressOptionsRequest"}}}},"responses":{"200":{"description":"The options the order could ship on to the new address.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShippingAddressOptions"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableMessage"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/orders/{id}/shipping_address":{"parameters":[{"$ref":"#/components/parameters/ActAs"},{"$ref":"#/components/parameters/OrderId"}],"patch":{"tags":["Orders"],"operationId":"updateOrderShippingAddress","summary":"Change an order's shipping address","x-mcp-description":"Changes data. Use to change where an order ships. Call\nlist_shipping_address_options first with the same `shipping_address`, then\nsend it again with one option's `arrival_date`, `shipping_option_name` and\n`fc`. The order moves to that date and service, and to that warehouse when\nthe option's `changes_fc` is true, so confirm the option with the person\nfirst. Send only the address fields that change. An option no longer offered\nis a 422: list the options again. To undo, change it back the same way.\n","description":"Send the new `shipping_address` (only the fields that change; a field\nsent empty is cleared) with the `arrival_date`, `shipping_option_name`\nand `fc` of one option from `POST\n/orders/{id}/shipping_address_options`. All three are required: the\norder moves to that date, service and warehouse, or nothing changes.\n\nThe options are worked out again when you send this, so one that is no\nlonger offered is a `422`; list the options again and pick another.\nLeaving out `phone_number` keeps the order's number. We don't tell\nyour customer about the change. A later `PUT /orders` for this order\nsends its address again and replaces this one, so update your own\nrecord of the order too.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateShippingAddressRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/UpdateShippingAddressRequest"}}}},"responses":{"200":{"description":"The order, at its new address and on the option you picked.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableMessage"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/orders/{id}/gift_message":{"parameters":[{"$ref":"#/components/parameters/ActAs"},{"$ref":"#/components/parameters/OrderId"}],"patch":{"tags":["Orders"],"operationId":"updateOrderGiftMessage","summary":"Change or remove an order's gift message","x-mcp-description":"Changes data. Use to change the gift message printed on the card in an\norder's box: send `gift_message` with the new text. Send it empty to remove\nthe card. Once the card is printed or the order is packed, the answer is a 422\nwith the reason. To undo, send the previous message, which the order's\n`gift_message` shows before the change.\n","description":"Replaces the gift message and the printed card that carries it.\n`gift_message` is required; an empty value removes the card. A\nmessage too long for the card, or an order whose card is already\nprinted or that is packed, is a `422` with the reason. Sending the\nmessage the order already has changes nothing. A later `PUT /orders`\nfor this order sends its gift message again and replaces this one.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftMessageRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/GiftMessageRequest"}}}},"responses":{"200":{"description":"The order, with its new gift message.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableMessage"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/shipment_calendar":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Quoting and dates"],"operationId":"getShipmentCalendar","summary":"Available fulfillment and arrival dates to a destination","x-mcp-description":"Use to see which ship and arrival dates are available to a ZIP code, without a\ncart: for \"when could something arrive in 98052?\". `weeks` (1 to 12) sets how\nfar ahead to look. Each plan has its ship date, arrival date, transit days,\ncarrier, service and FC; `default_plan` is the first preferred ground plan. It\nhas no prices: for what a cart would cost to ship, use create_quote.\n","description":"Independent of any cart — for rendering a delivery-date picker. 3PL\norganizations only; any other key is a `403`.\n","parameters":[{"name":"postal_code","in":"query","required":true,"description":"ZIP+4 and surrounding whitespace are normalized away. Fewer than\nthree digits, or a ZIP code that does not exist, is a `422`.\n","schema":{"type":"string"}},{"name":"weeks","in":"query","description":"Clamped to 1–12: a larger value is clamped, not rejected, and a\nnon-integer is a `422`.\n","schema":{"type":"integer","default":4}}],"responses":{"200":{"description":"Every plan inside the window, and the default pick.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShipmentCalendar"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableMessage"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/quote":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"post":{"tags":["Quoting and dates"],"operationId":"createQuote","summary":"Quote a cart: shipping options, dates and what each would cost","x-mcp-description":"Use to price a cart before it is ordered: send `destination.postal_code` and\n`items` (`sku_id`, `quantity`). It writes nothing. Each shipment (one per\nfulfillment center) lists `options`, a shipping service and delivery date each,\nwith `cost` at the partner's contracted rates. Pass `all_options: true` to\ncompare dates and services; otherwise you get the recommended option only. An\noption's `cost.total` covers that shipment only. The order-level additional pick\ncharge (each unit after the first) is NOT in any option's total: it appears\nonce, in the quote's top-level `cost.lines`, and is the same whichever option is\nchosen. So the cart's full cost is the chosen options' totals plus that line;\nthe top-level `cost.total` already adds it to the recommended options.\nOut-of-stock items come back in `unfulfillable`.\n","description":"Each shipment lists its `options` — one per shipping lane\n(`shipping_option_name`) and delivery date in your range, soonest first\n— and each option's `cost`\nis what it costs you: what Crowd Cow would bill at your contracted\nrates, itemised the way your invoice itemises it, plus the carrier's\nown rate when that option ships on your own carrier account. The box,\ndry ice and liner are estimated from your cart and destination.\n\n**Dates.** Each shipment's dates are counted from its own soonest\ndelivery. A range covers at most 7 days, inclusive; wider, `max`\nbefore `min`, a date in the past, or an unreadable date is a `422`.\nOnly `delivery_date_min`: that day and the 6 after it. Only\n`delivery_date_max`: the 7 days ending then, starting no earlier than\nthe shipment's soonest delivery. The same date in both: that day.\nNeither: the 7 days from the shipment's soonest delivery. When nothing\ncan arrive in the range, the shipment lists the 7 days from its\nsoonest delivery on or after `delivery_date_min` — never before it —\nor, with no `delivery_date_min`, from its soonest delivery, and its\n`reason` says so. When nothing can arrive on or after\n`delivery_date_min` at all, the quote is a `422`.\n\n**Tags.** Each is on exactly one option per shipment; an option can\ncarry several, and most carry none. `recommended` is the option an\norder for this cart is routed to: the cheapest shipping tier (ground,\nAxleHire and line-haul lanes, then 2nd Day Air, then Next Day Air),\nthen the soonest, among the options in the range we could price.\n`cheapest` is the lowest `cost.total`, ties to the sooner date.\n`fastest` is the soonest `delivery_date`, ties to the lower total.\nWithout `all_options` the list is the recommended option alone, tagged\n`recommended` only.\n\n**Every option is priced.** One we cannot price — a carrier rate lookup\nthat failed or ran out of time, or a carrier with no price before it\nships — is left out of the list. If that leaves a shipment with no\noptions, the quote is a `503` to retry when a carrier could not be\nreached, or a `422` `/problems/unpriceable` when no retry will price\nit. An organization with no rate card is a `422`.\n\n**Placing the order.** Send nothing, or send the `recommended`\noption's `delivery_date` as `preferred_arrival_date` on `PUT /orders`:\neither way the order is routed to that option. Another option's\n`delivery_date` lands the order on that day, on the cheapest service\nthat reaches it, when your organization's delivery dates are firm (the\ndefault); otherwise it picks the plan closest to that date, which may\nbe a cheaper service on a different day. To ship on a specific option,\nline-haul ones included, also send its `shipping_option_name` in\n`shipping_method`.\n\nThe request fields `requested_delivery_date`, `optimize_for` and\n`include_alternatives` were replaced by the date range, `all_options`\nand `tags`; a request that sends one is a `422` naming its replacement.\n\nA cart whose items are all out of stock still returns `200` with\n`\"shipments\": []`, so read `shipments`, `unfulfillable` and `advisory`\nrather than the status code. An **empty** `items` array is a `422`.\n3PL organizations only; any other key is a `403`.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/QuoteRequest"}}}},"responses":{"200":{"description":"The quote, as an estimate at quote time.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableMessage"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/RateUnavailable"}}}},"/openapi.json":{"get":{"tags":["Meta"],"operationId":"getOpenapiDocument","summary":"This document, as JSON","description":"The only endpoint that needs no API key.","security":[],"responses":{"200":{"description":"The OpenAPI 3.1 description of this API.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"type":"object"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/shipments":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Shipments"],"operationId":"listShipments","summary":"List your shipments, with tracking and delivery state","x-mcp-description":"Use for tracking and delivery state of boxes we shipped: where a package is,\nwhether it is late, which ones had problems. `status` is `not_shipped`,\n`in_transit`, `out_for_delivery` or `delivered`; `days_late` is measured against\n`promised_arrival`; `write_off` is `lost` or `destroyed_in_transit` when a box\nwill never arrive; `last_scan` is the latest carrier scan. Filter with `late`,\n`tracking_alert`, `never_arriving`, `carrier`, `order_id` or ship dates. An\norder not yet picked has no shipment. For counts, use summarize_shipments. For\nmore rows follow `next_cursor` until it is null, rather than raising `limit`.\n","description":"Tracking, delivery state and exception flags for the boxes we ship for you. The\nlist covers shipments that have been labeled or shipped; an order not yet picked\nhas no shipment row.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/shipments?status=in_transit\u0026limit=100' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\n**Key on `id`.** It is stable for the life of the shipment. `shipment_number` is\nthe box's position among the order's shipments as they stand today and can\nchange.\n\n`status` is `not_shipped`, `in_transit`, `out_for_delivery` or `delivered`.\n`promised_arrival` is the date we committed to, `predicted_arrival` the\ncarrier's current estimate. `days_late` is measured against `promised_arrival`,\nand is 0 for a written-off shipment. `write_off` is `null`, `\"lost\"` or\n`\"destroyed_in_transit\"`. `last_scan` is the latest carrier scan; scan history\nis not available.\n\nFilters are all optional and all combine. A value we cannot parse is a `400`\nnaming the parameter. Paging follows `next_cursor` until it is `null`. There is\nno total in the body — ask `/shipments/summary`, which takes the same filters.\nChanging a filter part-way through a walk is a `400` `/problems/cursor-invalid`;\nrestart from the first page.\n\n**Polling for what changed.** `updated_at_min` returns everything written since\nthe moment you name. When it is present the sort becomes `updated_at` ascending.\nKeep the highest `updated_at` you saw and send it back next run.\n\n```bash\n# 1. Everything that changed since the last run\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/shipments?updated_at_min=2026-06-03T14:02:11Z\u0026limit=200' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n\n# 2. Plus a periodic sweep of what is still moving\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/shipments?status=in_transit' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\n**Three fields change without a write.** `days_late`, the late flag and\n`promised_arrival` can all differ from what you last read while `updated_at`\nstands still — rescheduling the order changes the promise without changing the\nshipment. Use both calls above: `updated_at_min` for what changed, and a\nperiodic read of what is still in flight. All three are derivable from the row\nyou hold.\n\nA carrier scan updates the shipment, not the order. Poll **shipments** for\ndelivery state and **orders** for order-level changes.\n","parameters":[{"name":"q","in":"query","description":"Free text across the tracking number, your order id, your customer\norder id, our order number, the order token, and the recipient's\nname, city and postal code.\n","schema":{"type":"string"}},{"name":"order_id","in":"query","description":"Exact match on the `order_id` a row carries — your own identifier\nwhere you sent one, our order number otherwise.\n","schema":{"type":"string"}},{"name":"status","in":"query","description":"Delivery stage, mutually exclusive. An unrecognized value is a `400`\nnaming the four.\n","schema":{"type":"string","enum":["not_shipped","in_transit","out_for_delivery","delivered"]}},{"name":"carrier","in":"query","description":"Exact match on `carrier`. `/shipments/summary` returns the carriers\nyou have shipped with, which is the vocabulary this accepts.\n","schema":{"type":"string"}},{"name":"late","in":"query","description":"`true` keeps only late boxes, `false` keeps only boxes that are not\nlate. Anything else is a `400`, never a filter that did nothing.\n","schema":{"type":"boolean"}},{"name":"tracking_alert","in":"query","description":"Same, on whether the carrier has posted a problem scan.","schema":{"type":"boolean"}},{"name":"never_arriving","in":"query","description":"Same, on whether the box has been written off.","schema":{"type":"boolean"}},{"name":"ship_date_min","in":"query","description":"`YYYY-MM-DD`, inclusive, on the scheduled ship date. A date we\ncannot read is a `400`, never a dropped filter.\n","schema":{"type":"string"}},{"name":"ship_date_max","in":"query","description":"`YYYY-MM-DD`, inclusive, on the scheduled ship date.","schema":{"type":"string"}},{"name":"delivered_at_min","in":"query","description":"RFC 3339, inclusive. A timestamp rather than a date, so a poller can\nask about a moment rather than being handed the whole day.\n","schema":{"type":"string","format":"date-time"}},{"name":"delivered_at_max","in":"query","description":"RFC 3339, inclusive.","schema":{"type":"string","format":"date-time"}},{"name":"updated_at_min","in":"query","description":"RFC 3339, inclusive, on `updated_at`. When it is present the default\nsort becomes `updated_at` ascending, so a walk visits changes in the\norder they happened.\n","schema":{"type":"string","format":"date-time"}},{"name":"sort","in":"query","description":"`-` for descending. An unrecognized value is a `400`. Defaults to\n`-ship_date`, or `updated_at` when `updated_at_min` is given.\n","schema":{"type":"string","enum":["ship_date","-ship_date","carrier","-carrier","predicted_arrival","-predicted_arrival","delivered_at","-delivered_at","last_scan_at","-last_scan_at","updated_at","-updated_at","order_id","-order_id","destination","-destination","promised_arrival","-promised_arrival"]}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of matching shipments.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Link":{"$ref":"#/components/headers/LinkNext"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShipmentList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/shipments/summary":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Shipments"],"operationId":"summarizeShipments","summary":"The counts above the list, and the carriers you ship with","x-mcp-description":"Use for how many shipments there are (`total`), how many are `late`, have a\n`tracking_alert` or are `never_arriving` (lost or destroyed), and which\n`carriers` the partner ships with, without paging through list_shipments. It has\nno count per delivery stage; for that, filter list_shipments by `status`. It\ntakes the same filters as list_shipments, but the `status`, `late`,\n`tracking_alert` and `never_arriving` filters do not narrow the counts.\n","description":"The counts above the list. Takes the same filters as `GET /shipments`, but\ncounts are taken over `q`, the date range and `carrier`, before `status`,\n`late`, `tracking_alert` and `never_arriving` apply — so a count still describes\nthe whole set once you narrow by one of them. `carriers` names the carriers you\nship with, and is the vocabulary the `carrier` filter accepts.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/shipments/summary?ship_date_min=2026-06-01\u0026ship_date_max=2026-06-30' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\nEvery `/summary` sibling shares a smaller per-key budget: 60 requests per\nminute, separate from the 600 the rest of the API gets.\n","parameters":[{"name":"q","in":"query","schema":{"type":"string"}},{"name":"order_id","in":"query","schema":{"type":"string"}},{"name":"carrier","in":"query","schema":{"type":"string"}},{"name":"ship_date_min","in":"query","schema":{"type":"string"}},{"name":"ship_date_max","in":"query","schema":{"type":"string"}},{"name":"delivered_at_min","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"delivered_at_max","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"updated_at_min","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Counts over the filtered set, and the carriers you ship with.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShipmentSummary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/reports/delivery_performance":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Reports"],"operationId":"deliveryPerformanceReport","summary":"How your boxes arrived over a period, against the one before","x-mcp-description":"Use for how deliveries went over a period. `tiles` holds shipments, on-time\nrate, late, never arriving, tracking alerts and average transit days, each as a\n`current` and `prior` pair, so you can say whether things got better or worse.\n`period_start` and `period_end` (YYYY-MM-DD, on ship date) default to the last\ncompleted Sunday to Saturday week. Also returns a 13-week trend in `weeks` and\nthe problems we flagged in `findings`. For individual boxes, use list_shipments.\n","description":"How your boxes arrived over a period, against the period before it.\n`period_start` and `period_end` are optional; omit both for the default window,\nthe last completed week (Sunday–Saturday). One end on its own derives the other\n— a start alone runs seven days, an end alone runs back seven — and neither end\nruns past today.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/reports/delivery_performance?period_start=2026-08-01\u0026period_end=2026-08-31' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\nEach of the six headline measures returns as a **current and prior pair**.\n`weeks` is the trailing 13-week series, `findings` what we flagged in the\nperiod, and `order_status_counts` is per organization, not pooled across the\nones you trade under.\n\nOne request returns the whole report: no cursor. `/reports/*` shares a smaller\nper-key budget: 60 requests per minute, separate from the 600 the rest of the\nAPI gets.\n","parameters":[{"name":"period_start","in":"query","description":"`YYYY-MM-DD`, inclusive, on the scheduled ship date. A date we cannot\nread is a `400`, never a silently different period.\n","schema":{"type":"string"}},{"name":"period_end","in":"query","description":"`YYYY-MM-DD`, inclusive, on the scheduled ship date.","schema":{"type":"string"}}],"responses":{"200":{"description":"The report for the requested period.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryPerformance"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/delivery_feedback":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Delivery feedback"],"operationId":"listDeliveryFeedback","summary":"List your customers' delivery feedback reports","x-mcp-description":"Use to read what the partner's customers said about how their box arrived:\nbox and liner condition, whether the food was still frozen, dry ice left,\ndamage and a note, a 0-10 `recommend_score`, comments and photo links with\nour photo check's damage read. `problems=true` keeps reports of damage, soft\nor thawed food, or a box or liner that was not intact. Filter by `order_id`,\nor by `updated_at_min` for what changed since a moment. An order can have\nseveral reports. For the box's tracking use list_shipments. For more rows follow\n`next_cursor` until it is null, rather than raising `limit`.\n","description":"Every report your customers have sent on your orders, newest first. A\nreport still being filled in is not listed.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/delivery_feedback?problems=true' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\n**Key on `id`.** An order can have more than one report, and a customer\ncan change a sent report for 7 days after delivery. A change keeps the\n`id` and moves `updated_at`.\n\n`problems_reported` is `true` when the customer said the items were\ndamaged, the food was soft or thawed, or the box or liner was anything\nbut intact. `problems=true` keeps those reports.\n\n**Polling for what changed.** `updated_at_min` returns everything written\nsince the moment you name, oldest first. Keep the highest `updated_at` you\nsaw and send it back next run.\n\nFilters are all optional and all combine. A value we cannot parse is a\n`400` naming the parameter. Paging follows `next_cursor` until it is\n`null`; there is no total in the body.\n","parameters":[{"name":"order_id","in":"query","description":"Exact match on the `order_id` a row carries — your own identifier\nwhere you sent one, our order number otherwise.\n","schema":{"type":"string"}},{"name":"problems","in":"query","description":"`true` keeps only reports with a problem, `false` only reports\nwithout one. Anything else is a `400`.\n","schema":{"type":"boolean"}},{"name":"updated_at_min","in":"query","description":"RFC 3339, inclusive, on `updated_at`. When it is present the default\nsort becomes `updated_at` ascending.\n","schema":{"type":"string","format":"date-time"}},{"name":"sort","in":"query","description":"`-` for descending. Defaults to `-submitted_at`, or `updated_at`\nwhen `updated_at_min` is given.\n","schema":{"type":"string","enum":["submitted_at","-submitted_at","updated_at","-updated_at"]}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of reports.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Link":{"$ref":"#/components/headers/LinkNext"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryFeedbackList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/inventory_transactions":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Inventory transactions"],"operationId":"listInventoryTransactions","summary":"the inventory ledger — one row per unit movement","x-mcp-description":"Use for the inventory ledger: each movement of the partner's stock, newest\nfirst, with a signed `quantity` (negative leaves stock). `reason` is `delivery`\n(cases arriving at the warehouse and put into stock, not boxes delivered to\ncustomers), `receiving` (a correction to a receipt), `pick`, `cycle_count`,\n`sad_cow` (written off), `fc_transfer`, `pack_order`, `b2b_order`, `lost` or `initial`;\nany of them works as the `reason` filter. Filter by `sku_id`, `fc` and `occurred_at_min` /\n`occurred_at_max`. By default it counts only units the partner owns; `owner=all`\nadds Crowd Cow-owned units on their SKUs. For totals per SKU use\nsummarize_inventory_transactions. For more rows follow `next_cursor` until it is\nnull, rather than raising `limit`.\n","description":"Every movement of your stock in our warehouses: receipts, picks, cycle-count\ncorrections and write-offs, newest first.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/inventory_transactions?occurred_at_min=2026-08-01\u0026occurred_at_max=2026-08-31' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\n`quantity` is signed: receipts and upward corrections positive, picks and\nwrite-offs negative. `box_id`, `order_id` and `lot` are present when the\nmovement has one.\n\n`reason` is `delivery` when cases you sent us arrive and go into stock,\n`receiving` when we correct a receipt, `pick`, `cycle_count` or `sad_cow`, or\nfor any other movement its own name: `fc_transfer` when your stock moves\nbetween our centres, `pack_order`, `b2b_order` for a case leaving on a\nwholesale order, `lost` for picked units of a cancelled order that we could not\nfind to put back, and `initial` for a box's opening balance. Those are your\nunits moving, so they are listed rather than dropped, and each one works as\na `reason` filter. Only our internal prepacking step and zero-quantity rows\nare excluded.\n\n**The default scope is stock you own.** Your branded SKUs can also hold units\nCrowd Cow owns. This list, the month-end report and the Sad Cow report all count\nyour units only. `owner=all` widens the list to every movement on your SKUs;\neach row's `owner` says whose units moved.\n\nFilters are all optional and all combine. Paging follows `next_cursor` until it\nis `null`; there is no total in the body. Each row's `id` is stable, so an\noverlapping window can be de-duplicated on it.\n","parameters":[{"$ref":"#/components/parameters/MovementSkuId"},{"$ref":"#/components/parameters/MovementReason"},{"$ref":"#/components/parameters/MovementFc"},{"$ref":"#/components/parameters/MovementOccurredAtMin"},{"$ref":"#/components/parameters/MovementOccurredAtMax"},{"$ref":"#/components/parameters/MovementOwner"},{"name":"sort","in":"query","description":"Only `-occurred_at`. The ledger is read newest-first; asking for the\nother direction is a `400` rather than a page that quietly ignores\nyou.\n","schema":{"type":"string","enum":["-occurred_at"],"default":"-occurred_at"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of movements, newest first.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Link":{"$ref":"#/components/headers/LinkNext"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryTransactionList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/inventory_transactions/summary":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Inventory transactions"],"operationId":"summarizeInventoryTransactions","summary":"per-SKU movement totals over the same filters","x-mcp-description":"Use for totals per SKU over a date range: units put into stock (`delivery`),\nreceipt corrections (`receiving`), picked, counted and written off, plus `total`, from the same filters as\nlist_inventory_transactions. Movements with no bucket of their own, such as\n`fc_transfer`, count in `total` only. On the default owner scope with no\n`reason` filter, a month's totals equal the change between that month's opening\nand closing month-end inventory. One answer covers the whole range; there is no\npaging.\n","description":"The same filters as `GET /inventory_transactions`, totalled per SKU over the\nrange: one row per SKU, one field per reason, plus `total`. The whole filtered\nrange is summarized, not a page of it.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/inventory_transactions/summary?occurred_at_min=2026-08-01\u0026occurred_at_max=2026-08-31' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\nOn the default scope these totals reconcile: a month of movements equals the\ndifference between that month's opening and closing month-end snapshots. With\n`owner=all` they do not, because the snapshots count only your units.\n\nEvery `/summary` sibling shares a smaller per-key budget: 60 requests per\nminute, separate from the 600 the rest of the API gets.\n","parameters":[{"$ref":"#/components/parameters/MovementSkuId"},{"$ref":"#/components/parameters/MovementReason"},{"$ref":"#/components/parameters/MovementFc"},{"$ref":"#/components/parameters/MovementOccurredAtMin"},{"$ref":"#/components/parameters/MovementOccurredAtMax"},{"$ref":"#/components/parameters/MovementOwner"}],"responses":{"200":{"description":"One line per SKU with a movement in range.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InventoryTransactionSummary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/reports/month_end_inventory":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Reports"],"operationId":"getMonthEndInventoryReport","summary":"units on hand per SKU per fulfillment centre at a month end","x-mcp-description":"Use for how many units of each SKU the partner held at each fulfillment center\nat the close of a month, for accounting or reconciliation. `month` is YYYY-MM\nand defaults to the last completed month; a future month reports the current\none, and `as_of` says which moment you are reading. Counts units the partner\nowns only. One answer, no paging.\n","description":"Units of yours on hand at each fulfillment center at the close of a month.\n`month` is `YYYY-MM` and defaults to the last completed month. A future month\nreturns the current one rather than an error, and `as_of` always says which\nmoment you are reading.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/reports/month_end_inventory?month=2026-08' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\nCounts the stock you own, as inventory transactions does. Your movements over\nSeptember equal the difference between the August and September closes.\n\nOne request returns the whole report: no cursor, no summary sibling.\n`/reports/*` shares a smaller per-key budget: 60 requests per minute, separate\nfrom the 600 the rest of the API gets.\n","parameters":[{"name":"month","in":"query","description":"Any date inside the month — `2026-08` and `2026-08-15` both report\nAugust. Defaults to the most recently completed month. A future\nmonth reports the current one; a date we cannot read is a `400`.\n","schema":{"type":"string"},"examples":{"month":{"value":"2026-08"}}}],"responses":{"200":{"description":"One line per SKU holding stock at the cutoff.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonthEndInventoryReport"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/reports/sad_cow":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Reports"],"operationId":"getSadCowReport","summary":"units written off over a date range, per SKU and per reason","x-mcp-description":"Use for what was written off: units marked unsellable (\"sad cow\") in our\nwarehouses, per SKU, per fulfillment center and per reason, as positive counts.\n`occurred_at_min` / `occurred_at_max` (YYYY-MM-DD) default to the last completed\nweek. Stock refused at the receiving dock is not here, because it never became\nstock. One answer, no paging.\n","description":"Units written off, by SKU, by fulfillment center and by reason, as positive\ncounts. `occurred_at_min` and `occurred_at_max` default to the last completed\nweek.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/reports/sad_cow?occurred_at_min=2026-08-24\u0026occurred_at_max=2026-08-30' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\nCounts picking and counting write-offs, not stock refused at the receiving dock:\nthat product never became stock we held. Every unit here also appears in\ninventory transactions with reason `sad_cow`.\n\nOne request returns the whole report: no cursor. `/reports/*` shares a smaller\nper-key budget: 60 requests per minute, separate from the 600 the rest of the\nAPI gets.\n","parameters":[{"$ref":"#/components/parameters/ReportOccurredAtMin"},{"$ref":"#/components/parameters/ReportOccurredAtMax"}],"responses":{"200":{"description":"One line per SKU written off in the range.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SadCowReport"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/invoices":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Invoices"],"operationId":"listInvoices","summary":"your finalized 3PL invoices, newest period first","x-mcp-description":"Use to find the partner's issued invoices, newest billing period first: each\nwith its number (`id`), billing period, status and amounts. Drafts and voided\ninvoices never appear. Pass an `id` to get_invoice for its charge summary and\ndocument links. For more rows follow `next_cursor` until it is null, rather than\nraising `limit`.\n","description":"Your issued invoices, newest billing period first. Only issued invoices are\nreturned; drafts and voided invoices do not appear, and are not reachable by\nnumber either.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/invoices' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\n`id` is the invoice number printed on the document, and the identifier every\n`/invoices` path below takes. `status` is `sent` or `exported`. Money is a\ndecimal string in dollars with its `currency` beside it, never cents. `limit`\nand `cursor` work as they do on orders.\n","parameters":[{"name":"sort","in":"query","description":"Only `period_start`, ascending or descending. Defaults to newest\nperiod first.\n","schema":{"type":"string","enum":["period_start","-period_start"],"default":"-period_start"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of invoices.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Link":{"$ref":"#/components/headers/LinkNext"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/invoices/{id}":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Invoices"],"operationId":"getInvoice","summary":"one invoice, with its summary lines and document links","x-mcp-description":"Use for one invoice by its number: the period, totals, `line_items` (the summary\nof charges as billed, not a recalculation) and `documents`, links to the PDF and\nthe detail workbook. Give those links to the person, who opens them while signed\nin to the Crowd Cow portal; this tool cannot open them. For what each order or\nshipment was charged, use list_invoice_order_costs or\nlist_invoice_shipment_costs.\n","description":"The list row plus `line_items`, `bill_to`, `ship_to` and `documents`.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/invoices/10421' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\n`line_items` is the stored invoice's summary of charges, not a recalculation,\nand does not change when a rate card does. Per-charge detail is not inline; it\nis the two paged lists, `/order_costs` and `/shipment_costs`. This body is a\nfixed size whatever the invoice covers.\n","parameters":[{"$ref":"#/components/parameters/InvoiceNumber"}],"responses":{"200":{"description":"The invoice.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/InvoiceNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/invoices/{id}/order_costs":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Invoices"],"operationId":"listInvoiceOrderCosts","summary":"The invoice's Order Costs rows, one per billed order","x-mcp-description":"Use for the per-order charges on an invoice: one row per billed order with its\nitem labor (item count and rate) and marketplace figures, at the rates frozen\nwhen the invoice was made. An order's shipping, packaging and dry ice are not\nhere; they are on its rows in list_invoice_shipment_costs, so an order's full\ncharge is this row plus those. For more rows follow `next_cursor` until it is\nnull, rather than raising `limit`.\n","description":"What each order on the invoice cost, one row per billed order, in a stable\norder. Paged.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/invoices/10421/order_costs?limit=200' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\nThe rates are the frozen ones the invoice was calculated from; the delivery\naddress and order numbers are read live, so a later address correction shows\nhere without changing what you were charged.\n\nThe cost lists share a smaller per-key budget: 60 requests per minute, separate\nfrom the 600 the rest of the API gets.\n","parameters":[{"$ref":"#/components/parameters/InvoiceNumber"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of order-cost rows.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Link":{"$ref":"#/components/headers/LinkNext"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCostList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/InvoiceNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/invoices/{id}/shipment_costs":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Invoices"],"operationId":"listInvoiceShipmentCosts","summary":"The invoice's Shipment Costs rows, one per billed shipment","x-mcp-description":"Use for what each shipment on an invoice was charged: one row per billed box,\noldest shipped first, with its labor, line haul, last mile, packaging, dry\nice, gel pack, liner and printing charges split out as on the detail workbook. Use it for shipping costs by\ncarrier, service or box. Per-order charges are in list_invoice_order_costs. For\nmore rows follow `next_cursor` until it is null, rather than raising `limit`.\n","description":"The same, per shipment: one row per shipment billed on this invoice, oldest\nshipped first, with the frozen per-shipment charges split into the columns the\ndetail workbook uses. Paged; walk it with `next_cursor`.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/invoices/10421/shipment_costs?limit=200' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\nThe cost lists share a smaller per-key budget: 60 requests per minute, separate\nfrom the 600 the rest of the API gets.\n","parameters":[{"$ref":"#/components/parameters/InvoiceNumber"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"A page of shipment-cost rows.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Link":{"$ref":"#/components/headers/LinkNext"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShipmentCostList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/InvoiceNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/invoices/{id}/document.pdf":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Invoices"],"operationId":"getInvoicePdf","summary":"the invoice PDF we emailed you","description":"Takes your API key alone and answers `302` to the stored file, so follow\nredirects.\n\n```bash\ncurl --location 'https://www.crowdcow.com/api/fulfillment/v1/invoices/10421/document.pdf' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e' \\\n  --output invoice-10421.pdf\n```\n\n**You get the stored document, not one rendered on request.** This is the copy\nmade when the invoice was emailed, so it cannot disagree with the copy in your\ninbox. An issued invoice with no stored copy answers `404` with a `detail`\nsaying it was never emailed. Retrying will not produce one; send us the invoice\nnumber.\n\nThe document routes share a smaller per-key budget: 60 requests per minute,\nseparate from the 600 the rest of the API gets.\n","parameters":[{"$ref":"#/components/parameters/InvoiceNumber"}],"responses":{"302":{"$ref":"#/components/responses/StoredDocument"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/InvoiceNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/invoices/{id}/detail.xlsx":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["Invoices"],"operationId":"getInvoiceDetailWorkbook","summary":"the invoice detail workbook we emailed you","description":"Takes your API key alone and answers `302` to the stored spreadsheet — Summary,\nOrder Costs and Shipment Costs in one file, the same rows the endpoints above\nreturn. Follow redirects.\n\n```bash\ncurl --location 'https://www.crowdcow.com/api/fulfillment/v1/invoices/10421/detail.xlsx' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e' \\\n  --output invoice-10421.xlsx\n```\n\n**You get the stored document, not one rendered on request.** This is the copy\nmade when the invoice was emailed. An issued invoice with no stored copy answers\n`404` with a `detail` saying it was never emailed. Retrying will not produce one;\nsend us the invoice number.\n\nThe document routes share a smaller per-key budget: 60 requests per minute,\nseparate from the 600 the rest of the API gets.\n","parameters":[{"$ref":"#/components/parameters/InvoiceNumber"}],"responses":{"302":{"$ref":"#/components/responses/StoredDocument"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/InvoiceNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/skus/{sku_id}":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["SKUs"],"operationId":"getSku","summary":"One SKU, with what is committed against it","x-mcp-description":"Use for one SKU by the partner's own SKU identifier: its stock per fulfillment\ncenter plus `pending_reservations` (units held for the partner's orders and not\nyet picked) and `packed_not_shipped`. Both count the partner's orders only, so\n`pending_reservations` can be lower than `reserved_quantity`, which counts every\norder holding the SKU. An unknown SKU is a 404.\n","description":"One SKU by your own identifier, plus two figures the list does not carry:\n`pending_reservations` and `packed_not_shipped`.\n\n```bash\ncurl 'https://www.crowdcow.com/api/fulfillment/v1/skus/s_ribeye_12oz' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\nBoth count **your** orders only. Units of the same SKU committed to another\norganization's order are not included, so `pending_reservations` can be\nlower than the `reserved_quantity` in `inventory`, which counts every\norder holding the SKU.\n","parameters":[{"name":"sku_id","in":"path","required":true,"description":"Your own SKU identifier.","schema":{"type":"string"}}],"responses":{"200":{"description":"The SKU.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SkuDetail"}}}},"404":{"$ref":"#/components/responses/SkuNotFound"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/skus/{sku_id}/archive":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"post":{"tags":["SKUs"],"operationId":"archiveSku","x-status":"coming","summary":"Coming: archive a SKU","description":"Archiving retires a SKU from the working list. History, movements and past\norders are untouched, and the SKU stays readable by id.\n\n```bash\ncurl --request POST 'https://www.crowdcow.com/api/fulfillment/v1/skus/s_ribeye_12oz/archive' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n\n**`GET /skus` excludes archived SKUs by default.** `archived=include` returns\nthem alongside the active ones; `archived=only` returns just them.\n","parameters":[{"name":"sku_id","in":"path","required":true,"description":"Your own SKU identifier.","schema":{"type":"string"}}],"responses":{"200":{"description":"The SKU, with `archived` updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sku"}}}},"404":{"$ref":"#/components/responses/NotFound"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/skus/{sku_id}/unarchive":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"post":{"tags":["SKUs"],"operationId":"unarchiveSku","x-status":"coming","summary":"Coming: unarchive a SKU","description":"Returns an archived SKU to the default `GET /skus` list. History, movements and\npast orders are unaffected either way.\n\n```bash\ncurl --request POST 'https://www.crowdcow.com/api/fulfillment/v1/skus/s_ribeye_12oz/unarchive' \\\n  --header 'X-Api-Key: \u003cYOUR_TOKEN\u003e'\n```\n","parameters":[{"name":"sku_id","in":"path","required":true,"description":"Your own SKU identifier.","schema":{"type":"string"}}],"responses":{"200":{"description":"The SKU, with `archived` updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Sku"}}}},"404":{"$ref":"#/components/responses/NotFound"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/api_keys":{"parameters":[{"$ref":"#/components/parameters/ActAs"}],"get":{"tags":["API keys"],"operationId":"listApiKeys","summary":"Coming: list your API keys","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"PortalSession":[],"OrganizationHeader":[]}],"description":"Every key on your organization, newest first, revoked ones included.\nNo token is returned; `name` and `prefix` are how you tell keys apart.\n\n```bash\ncurl https://www.crowdcow.com/api/fulfillment/v1/api_keys \\\n  -H \"X-Api-Key: $CC_API_KEY\"\n```\n\nAn organization admin signed in to the partner portal calls the same\nendpoint with the session cookie and `CrowdCow-Organization: seabear`\nin place of the key.\n","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"Your organization's keys.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"Link":{"$ref":"#/components/headers/LinkNext"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"No usable credential: no API key and no partner-portal session. A\nkey that has been revoked, or has passed its `expires_at`, lands\nhere too, as does an expired session.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential cannot act for this organization: a key belongs to a\ndifferent one, or your session has no access to the organization\nnamed in `CrowdCow-Organization`. A key never reaches another\norganization's keys, whoever holds it.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}},"x-status":"coming"},"post":{"tags":["API keys"],"operationId":"createApiKey","summary":"Coming: create an API key","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"PortalSession":[],"OrganizationHeader":[]}],"description":"Issues a key and returns its token **once**. Store the token before you\nclose the response — we keep only a hash of it.\n\nName the key after the integration that will hold it, so\n`last_used_at` on a list tells you which system is still calling.\n\n```bash\ncurl -X POST https://www.crowdcow.com/api/fulfillment/v1/api_keys \\\n  -H \"X-Api-Key: $CC_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: 5f4d0f2e-2c4b-4a1e-9d3a-7b6c0e1f2a34\" \\\n  -d '{\"name\": \"Order push, production\"}'\n```\n\nA blank or duplicate `name` is a `422`; so is a name that is only\nwhitespace. Names have to be unique among the keys that are live: a\nrevoked key's name is free to reuse, and so is the name of a key inside\na rotation overlap window.\n\nThe new key is created on the caller's own organization — the one the\nkey belongs to, or the one a portal session names in\n`CrowdCow-Organization`. There is no way to create a key anywhere else.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/CreateApiKeyRequest"}}}},"responses":{"201":{"description":"The new key, with its token.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"401":{"description":"No usable credential: no API key and no partner-portal session. A\nkey that has been revoked, or has passed its `expires_at`, lands\nhere too, as does an expired session.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential cannot act for this organization: a key belongs to a\ndifferent one, or your session has no access to the organization\nnamed in `CrowdCow-Organization`. A key never reaches another\norganization's keys, whoever holds it.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"`name` was blank, whitespace only, longer than 100 characters, or\nalready in use; or `expires_at` is not in the future. A name is in\nuse when another key on this organization holds it and is neither\nrevoked nor inside a rotation overlap window.\n\nAn `Idempotency-Key` already used with a different body is a\n`/problems/idempotency-key-reused`, also a `422`. The same key with\nthe same body is not an error — it replays the original response.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}},"x-status":"coming"}},"/api_keys/{id}/rotate":{"parameters":[{"$ref":"#/components/parameters/ActAs"},{"$ref":"#/components/parameters/ApiKeyId"}],"post":{"tags":["API keys"],"operationId":"rotateApiKey","summary":"Coming: rotate an API key","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"PortalSession":[],"OrganizationHeader":[]}],"description":"Issues a replacement key and schedules the old one's revocation for\n`overlap_hours` from now, so both work while you deploy the new token.\nThe replacement carries the same `name`, the same `response_format`\n**copied from the key being rotated** rather than derived from your\norganization, and the same `expires_at` unless the request sets one —\nso rotating never moves an integration onto a response shape it has\nnever parsed, and never quietly makes a key permanent.\n\nThe response carries the new key's token — once — and the superseded\nkey with its `expires_at` set. Watch `last_used_at` on the superseded\nkey to confirm nothing is still using it; `DELETE` it early once\nnothing is.\n\n```bash\ncurl -X POST https://www.crowdcow.com/api/fulfillment/v1/api_keys/key_7Qw2mJ4fT1/rotate \\\n  -H \"X-Api-Key: $CC_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: 9c1b7a30-8e52-4f6d-b0a4-15c2e7d93f80\" \\\n  -d '{\"overlap_hours\": 48}'\n```\n\nA key may rotate itself, and that is the ordinary case for an\nunattended integration. Rotating the key you are calling with always\nleaves it working for the overlap window, so `overlap_hours: 0` on your\nown key is refused with a `422` unless the organization has another\nactive key to fall back on.\n\nRotating an already-revoked key is a `422`.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateApiKeyRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/RotateApiKeyRequest"}}}},"responses":{"200":{"description":"The replacement key with its token, and the superseded key.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyRotated"}}}},"401":{"description":"No usable credential: no API key and no partner-portal session. A\nkey that has been revoked, or has passed its `expires_at`, lands\nhere too, as does an expired session.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential cannot act for this organization: a key belongs to a\ndifferent one, or your session has no access to the organization\nnamed in `CrowdCow-Organization`. A key never reaches another\norganization's keys, whoever holds it.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No key with that `id` on this organization.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The key is already revoked, so there is nothing to rotate; or `name`\nis blank, whitespace only, too long, or already in use; or\n`expires_at` is not in the future. The key being rotated never\ncounts against its own successor, so the default — a successor that\nkeeps the name — is not a duplicate and is never refused as one.\n\n`overlap_hours: 0` on the key you are calling with is refused here\nunless another active key exists, so a rotation cannot end the\nrequest that started it.\n\nAn `Idempotency-Key` already used with a different body is a\n`/problems/idempotency-key-reused`, also a `422`.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}},"x-status":"coming"}},"/api_keys/{id}":{"parameters":[{"$ref":"#/components/parameters/ActAs"},{"$ref":"#/components/parameters/ApiKeyId"}],"delete":{"tags":["API keys"],"operationId":"revokeApiKey","summary":"Coming: revoke an API key","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]},{"PortalSession":[],"OrganizationHeader":[]}],"description":"Stops the key immediately. Requests carrying it get `401` from the next\none onwards, and there is no undo — issue a new key instead.\n\nRevoking an already-revoked key is a no-op that returns `200` with the\nsame object, so it is safe to retry, and it needs no `Idempotency-Key`\nfor the same reason.\n\nA portal session may revoke any key on the organization, its last\nactive one included: portal sign-in does not depend on a key. A key may\nrevoke any key on its own organization **except itself while it is the\nonly active one** — that request is a `422`, because it would leave the\norganization with no way back in without a person.\n","responses":{"200":{"description":"The revoked key.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"},"example":{"id":"key_7Qw2mJ4fT1","name":"Order push, production","prefix":"3f9ac21d","response_format":"modern","created_at":"2026-04-02T18:20:11Z","last_used_at":"2026-09-10T15:42:03Z","revoked_at":"2026-09-10T17:04:55Z"}}}},"401":{"description":"No usable credential: no API key and no partner-portal session. A\nkey that has been revoked, or has passed its `expires_at`, lands\nhere too, as does an expired session.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential cannot act for this organization: a key belongs to a\ndifferent one, or your session has no access to the organization\nnamed in `CrowdCow-Organization`. A key never reaches another\norganization's keys, whoever holds it.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No key with that `id` on this organization.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"A key tried to revoke itself while it was the only active key on the\norganization. Create the replacement first, or rotate instead.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}},"x-status":"coming"}}},"components":{"securitySchemes":{"ApiKeyAuth":{"description":"Your key in an `X-Api-Key` header.","type":"apiKey","in":"header","name":"X-Api-Key"},"BearerAuth":{"description":"The same key as a bearer token. Either header works; they are one\ncredential, not two.\n","type":"http","scheme":"bearer"},"PartnerOAuth":{"description":"An OAuth access token, sent as `Authorization: Bearer`, that an\nAI assistant receives when one of your people connects it to Crowd Cow.\nThey sign in as themselves and choose one organization; the token can\ncall every operation here for that organization, as that person, and\nstops working when they lose access to it. Tokens last an hour and\nrefresh.\n","type":"oauth2","flows":{"authorizationCode":{"authorizationUrl":"https://www.crowdcow.com/oauth/authorize","tokenUrl":"https://www.crowdcow.com/oauth/token","refreshUrl":"https://www.crowdcow.com/oauth/token","scopes":{"partner_mcp":"Act for one organization, as the person who connected."}}}},"PortalSession":{"description":"**Coming.** A partner-portal session cookie, set when an organization\nadmin signs in at crowdcow.com. Only the **API keys** operations take\nit, and they take a key as well — it is the alternative for a person\nmanaging credentials by hand, not a second credential every endpoint\nunderstands. Send it together with `CrowdCow-Organization`.\n","type":"apiKey","in":"cookie","name":"_cc_session"},"OrganizationHeader":{"description":"**Coming.** The organization you are acting for, by the code name in\nyour portal URL — `seabear` in `/fulfillment/seabear/…`. Paired with\n`PortalSession`, because one person can hold access to more than one\norganization and a key management call must never be ambiguous about\nwhich.\n","type":"apiKey","in":"header","name":"CrowdCow-Organization"}},"parameters":{"ActAs":{"name":"CrowdCow-Act-As","in":"header","required":false,"description":"For Crowd Cow's own assistant only: the email of the partner portal login a\nrequest acts for. Refused from any other key; leave it out.\n","schema":{"type":"string"}},"OrderId":{"name":"id","in":"path","required":true,"description":"Your own `order_id`, as sent to `PUT /orders`.","schema":{"type":"string"}},"Limit":{"name":"limit","in":"query","description":"Page size. A value above the maximum is clamped rather than refused.\nZero, a negative and a non-integer are each a `400`.\n","schema":{"type":"integer","default":50,"maximum":200}},"Cursor":{"name":"cursor","in":"query","description":"The `next_cursor` from the previous page. Opaque: do not construct or\nparse one. It carries the filters it was issued under, so changing a\nfilter part-way through a walk is a `400 /problems/cursor-invalid`.\nStart the walk again from the first page when you see it.\n","schema":{"type":"string"}},"ApiKeyId":{"name":"id","in":"path","required":true,"description":"The key's `id`, as returned by `GET /api_keys`. Not the token, and safe\nto put in a log or a URL.\n","schema":{"type":"string"}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"description":"A string you choose for this request and reuse only to retry it — a\nUUID is the usual choice. Retrying with the same key returns the\noriginal response, token included, instead of issuing a second key; we\nkeep the result for 24 hours, after which the same key is a new\nrequest. The same key with a different body is a\n`422 /problems/idempotency-key-reused`.\n","schema":{"type":"string","maxLength":255}},"InvoiceNumber":{"name":"id","in":"path","required":true,"description":"The `invoice_number` printed on the document — the number your\naccounts-payable team already has. Not a database id; there is no\nendpoint that takes one.\n","schema":{"type":"string"}},"MovementSkuId":{"name":"sku_id","in":"query","description":"Your own SKU identifier. Repeat as `sku_id[]=` or comma-separate — a\nbare repeated `sku_id=` keeps only the last value, which is Rack, not\nus.\n","schema":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}},"MovementReason":{"name":"reason","in":"query","description":"Narrow to one kind of movement: any `reason` a row can carry. `pick`\ncovers picks out of a prepacked case as well as ordinary ones.\n","schema":{"type":"string","enum":["receiving","pick","cycle_count","sad_cow","delivery","fc_transfer","pack_order","b2b_order","lost","initial"]}},"MovementFc":{"name":"fc","in":"query","description":"A fulfillment centre code — `dunmore` is Scranton, PA.","schema":{"type":"string"}},"ReportOccurredAtMin":{"name":"occurred_at_min","in":"query","description":"Inclusive start of the range, `YYYY-MM-DD`. `min_date` is accepted as an\nalias. Give neither end and you get the most recently completed\nSunday–Saturday week; give only this one and the range runs to today. A\nstart in the future collapses to today.\n","schema":{"type":"string","format":"date"}},"ReportOccurredAtMax":{"name":"occurred_at_max","in":"query","description":"Inclusive end of the range, covering the whole Pacific day. `max_date`\nis accepted as an alias. Give only this one and the range opens back to\nyour first write-off — never past this end. A future end clamps to\ntoday; the response's `period` always says what was actually read.\n","schema":{"type":"string","format":"date"}},"MovementOccurredAtMin":{"name":"occurred_at_min","in":"query","description":"Inclusive lower bound, `YYYY-MM-DD`, read in Pacific time. `min_date` is\naccepted as an alias. Unparseable is a `400`, never a silently ignored\nfilter.\n","schema":{"type":"string","format":"date"}},"MovementOccurredAtMax":{"name":"occurred_at_max","in":"query","description":"Inclusive upper bound, covering the whole Pacific day — a movement at\n23:50 on the last day is in. `max_date` is accepted as an alias.\n","schema":{"type":"string","format":"date"}},"MovementOwner":{"name":"owner","in":"query","description":"`mine` (the default) is stock you own; `all` widens to every movement on\nyour SKUs, including Crowd Cow-owned units. Only `mine` reconciles\nagainst the month-end snapshot.\n","schema":{"type":"string","enum":["mine","all"],"default":"mine"}}},"headers":{"RateLimit":{"description":"Where you stand against whichever limit is closest to running out, per\ndraft-ietf-httpapi-ratelimit-headers-11 — e.g. `\"per-key\";r=597;t=42`.\n`r` is the requests left, `t` the seconds until the window resets.\n","required":true,"schema":{"type":"string"}},"RateLimitPolicy":{"description":"The quota and window of that same limit — e.g. `\"per-key\";q=600;w=60`.\nThe policy is named `per-key`, `per-key-reports`, `per-ip` or\n`per-ip-total`.\n","required":true,"schema":{"type":"string"}},"RetryAfter":{"description":"Whole seconds to wait before retrying. Always present on a 429 and a\n503, and always a positive integer.\n","required":true,"schema":{"type":"string"}},"LinkNext":{"description":"RFC 8288 `\u003c…\u003e; rel=\"next\"`, the same page `next_cursor` points at.\nAbsent on the last page, which is how a header-following client knows\nthe walk is over.\n","required":false,"schema":{"type":"string"}}},"responses":{"Unauthorized":{"description":"No API key, or one we do not recognize. Always this shape, whatever key\nyou hold.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"Forbidden":{"description":"Your key cannot reach this endpoint: quote, shipment calendar,\nshipments and delivery performance are 3PL accounts only, and the SKU,\ninventory transaction, month-end and Sad Cow endpoints need an account\nwith a fulfillment vendor.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"NotFound":{"description":"No order with that `order_id` in your account.","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"SkuNotFound":{"description":"No SKU of yours carries that identifier. A SKU that belongs to another\norganization answers the same way.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"BadRequest":{"description":"A parameter was unreadable, named a value that is not in its\nvocabulary, or the cursor was not one we issued.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"UnprocessableMessage":{"description":"A request body was unreadable, or the order is too far along for the\nchange.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"UnprocessableErrors":{"description":"The upsert failed. `errors` carries one entry per thing that was wrong.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"TooManyRequests":{"description":"A rate limit was exceeded. Always this shape, whatever key you hold.\n`Retry-After` says how long to wait.\n","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"RateUnavailable":{"description":"No shipping option could be priced right now: a carrier rate lookup\nfailed or ran out of time. `/problems/rate-unavailable`; retry after\n`Retry-After`.\n","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"InvoiceNotFound":{"description":"No invoice of yours carries that number. A draft or voided invoice\nanswers the same way: neither is a document you hold, so neither is\nlisted or reachable. A document endpoint also answers this when the\ninvoice exists but was never emailed, so no file was ever stored — the\n`detail` says which case it is.\n","headers":{"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"StoredDocument":{"description":"A redirect to the stored file. Follow `Location`; it is a time-limited\nURL, so fetch it rather than saving it.\n","headers":{"Location":{"description":"Where the stored file is.","required":true,"schema":{"type":"string"}},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"}},"content":{"text/html":{"schema":{"type":"string"}}}}},"schemas":{"Problem":{"description":"RFC 9457. Branch on `type`, never on the wording of `detail` — `detail`\nis written for a person and may be reworded without notice.\n","type":"object","additionalProperties":false,"required":["type","title","status"],"properties":{"type":{"type":"string","description":"A relative, non-resolvable problem URI, e.g. `/problems/not-found`.","enum":["/problems/unauthorized","/problems/forbidden","/problems/not-found","/problems/validation-error","/problems/idempotency-key-reused","/problems/policy-refused","/problems/cursor-invalid","/problems/quota-exceeded","/problems/no-rate-card","/problems/rate-unavailable","/problems/unpriceable"]},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string","description":"`/requests/\u003cid\u003e` — quote it to us and we can find the request.\n"},"errors":{"description":"One entry per field that failed validation of a request body.","type":"array","items":{"type":"object","additionalProperties":false,"required":["code","detail"],"properties":{"pointer":{"type":"string"},"code":{"type":"string"},"detail":{"type":"string"}}}},"reasons":{"description":"Why the change was refused, in plain sentences.","type":"array","items":{"type":"string"}}},"example":{"type":"/problems/validation-error","title":"Request is not valid.","status":400,"detail":"status must be one of: not_packed, packed, shipped, cancelled","instance":"/requests/7f1c3e2a-4b8d-4e91-9c2a-1f5e8d3b7a60"}},"OrderList":{"description":"A page of orders. No total is returned; walk with `next_cursor`.","type":"object","additionalProperties":false,"required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Order"}},"has_more":{"type":"boolean","description":"Whether a further page exists."},"next_cursor":{"type":["string","null"],"description":"Pass as `cursor` for the next page; `null` on the last one."}},"example":{"data":[{"fc":"dunmore","order_id":"1234567","customer_order_id":"456","scheduled_fulfillment_date":"2026-06-02","can_modify":true,"cancelled_at":null,"shipping_method":{"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS"},"packaging":{"dry_ice_in_pounds":10},"items":[{"sku":"s_ribeye_12oz","quantity":2,"weight":"12 oz"},{"sku":"s_bison_burger_8pk","quantity":1,"weight":4.0}],"shipping_address":{"name":"John Doe","address_1":"123 Main St","address_2":"Apt 1","city":"Kirkland","state":"WA","postal_code":"98033","phone_number":"(206) 555-1212","delivery_instructions":"Gate code 4432"},"sender_name":"Jane Roe","gift_message":"Happy birthday, John!","billing_address":{"address_1":"123 Billing St","address_2":"Suite 500","city":"Seattle","state":"WA","postal_code":"98104"},"shipments":[{"tracking_number":"1Z999AA10123456784","carrier":"UPS","service":"Ground","tracking_url":"https://www.ups.com/track?tracknum=1Z999AA10123456784","anticipated_delivery_date":"2026-06-04","delivered_at":"2026-06-04T19:47:00Z","shipped_at":"2026-06-02T01:31:00Z"}],"updated_at":"2026-06-03T14:02:11Z"},{"fc":"dallas","order_id":"1234568","customer_order_id":"457","scheduled_fulfillment_date":"2026-06-09","can_modify":false,"cancelled_at":"2026-06-06T16:40:00Z","shipping_method":{"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS"},"packaging":{"dry_ice_in_pounds":10},"items":[{"sku":"s_bison_burger_8pk","quantity":2,"weight":4.0}],"shipping_address":{"name":"John Doe","address_1":"123 Main St","address_2":"Apt 1","city":"Kirkland","state":"WA","postal_code":"98033","phone_number":"(206) 555-1212","delivery_instructions":"Gate code 4432"},"sender_name":"Jane Roe","gift_message":"Enjoy!","billing_address":{"address_1":"123 Billing St","address_2":"Suite 500","city":"Seattle","state":"WA","postal_code":"98104"},"shipments":[],"updated_at":"2026-05-29T09:15:00Z"}],"has_more":true,"next_cursor":"eyJ2IjoiMjAyNi0wNi0wMiJ9"}},"SkuList":{"type":"object","additionalProperties":false,"required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Sku"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"example":{"data":[{"id":"s_ribeye_12oz","name":"Ribeye 12oz","weight":0.75,"barcode":"0123456789012","width":2.0,"length":1.0,"height":3.0,"inventory":{"dunmore":{"on_hand_quantity":40,"reserved_quantity":12,"available_quantity":28},"dallas":{"on_hand_quantity":0,"reserved_quantity":0,"available_quantity":0},"watsonville":{"on_hand_quantity":15,"reserved_quantity":3,"available_quantity":12}}},{"id":"s_bison_burger_8pk","name":"Bison Burger 8pk","weight":2.0,"barcode":"0123456789029","width":6.0,"length":8.0,"height":2.5,"inventory":{"dunmore":{"on_hand_quantity":22,"reserved_quantity":4,"available_quantity":18},"dallas":{"on_hand_quantity":9,"reserved_quantity":1,"available_quantity":8},"watsonville":{"on_hand_quantity":0,"reserved_quantity":0,"available_quantity":0}}}],"has_more":true,"next_cursor":"eyJ2IjoiMjAyNi0wOC0xNCJ9"}},"FcCode":{"type":["string","null"],"description":"A fulfillment center code as this API spells it — `dunmore` is Scranton, PA.\n","examples":["dunmore"],"example":"dunmore"},"Sku":{"allOf":[{"$ref":"#/components/schemas/SkuFields"}],"unevaluatedProperties":false},"SkuDetail":{"description":"A SKU as `GET /skus/{sku_id}` returns it: every field of the list's\nSKU, plus what your own orders have committed against it.\n","allOf":[{"$ref":"#/components/schemas/SkuFields"},{"type":"object","required":["pending_reservations","packed_not_shipped"],"properties":{"pending_reservations":{"$ref":"#/components/schemas/SkuCommittedUnits"},"packed_not_shipped":{"$ref":"#/components/schemas/SkuCommittedUnits"}}}],"unevaluatedProperties":false,"example":{"id":"s_ribeye_12oz","name":"Ribeye 12oz","weight":0.75,"barcode":"0123456789012","width":2.0,"length":1.0,"height":3.0,"inventory":{"dunmore":{"on_hand_quantity":40,"reserved_quantity":12,"available_quantity":28},"dallas":{"on_hand_quantity":0,"reserved_quantity":0,"available_quantity":0},"watsonville":{"on_hand_quantity":15,"reserved_quantity":3,"available_quantity":12}},"pending_reservations":{"total":14,"by_fc":{"dunmore":11,"dallas":0,"watsonville":3}},"packed_not_shipped":{"total":2,"by_fc":{"dunmore":2,"dallas":0,"watsonville":0}}}},"SkuCommittedUnits":{"type":"object","additionalProperties":false,"description":"Units on your own orders, in total and per fulfillment center.\n`pending_reservations` is units reserved for orders and not yet\npicked; `packed_not_shipped` is units packed for an order none of whose\nboxes has shipped. Packed units are counted per order, not per box: once\nany box of an order ships, that order's packed units leave this figure.\n","required":["total","by_fc"],"properties":{"total":{"type":"integer","description":"The sum of `by_fc`."},"by_fc":{"type":"object","description":"Keyed by fulfillment center code. Every key in `inventory` is\npresent, reading 0 where none of these units are; a center outside\n`inventory` that holds some of them appears as well, so the keys\nalways add up to `total`.\n","additionalProperties":{"type":"integer"}}},"example":{"total":14,"by_fc":{"dunmore":11,"dallas":0,"watsonville":3}}},"SkuFields":{"type":"object","required":["id","name","weight","barcode","width","length","height","inventory"],"properties":{"id":{"type":["string","null"],"description":"Your own SKU identifier."},"name":{"type":["string","null"]},"weight":{"type":["number","null"],"description":"Pounds."},"barcode":{"type":["string","null"]},"width":{"type":["number","null"],"description":"Inches."},"length":{"type":["number","null"],"description":"Inches."},"height":{"type":["number","null"],"description":"Inches."},"inventory":{"type":"object","description":"Keyed by fulfillment center code.","additionalProperties":{"$ref":"#/components/schemas/SkuInventory"}},"archived":{"type":"boolean","x-status":"coming","description":"Coming: whether the SKU is archived. Archived SKUs will be excluded\nfrom the list unless you ask for them.\n"},"perishable":{"type":"boolean","x-status":"coming","description":"Coming: whether the SKU is perishable."},"internal_notes":{"type":"string","x-status":"coming","description":"Coming: your own notes, writable through `PUT /skus`."},"images":{"type":"array","items":{"type":"string"},"x-status":"coming","description":"Coming: image URLs."},"weeks_of_stock":{"type":"object","x-status":"coming","description":"Coming: cover at current sales velocity — `total`, and one entry per\nfulfillment center.\n","properties":{"total":{"type":"number"},"by_fc":{"type":"object","additionalProperties":{"type":"number"}}}},"inventory_cost":{"type":"object","x-status":"coming","description":"Coming: your cost basis per fulfillment center. Omitted for a SKU\nyou resell rather than own.\n","additionalProperties":{"type":"string"}}},"example":{"id":"s_ribeye_12oz","name":"Ribeye 12oz","weight":0.75,"barcode":"0123456789012","width":2.0,"length":1.0,"height":3.0,"inventory":{"dunmore":{"on_hand_quantity":40,"reserved_quantity":12,"available_quantity":28},"dallas":{"on_hand_quantity":0,"reserved_quantity":0,"available_quantity":0},"watsonville":{"on_hand_quantity":15,"reserved_quantity":3,"available_quantity":12}}}},"SkuInventory":{"type":"object","additionalProperties":false,"required":["on_hand_quantity","reserved_quantity","available_quantity"],"properties":{"on_hand_quantity":{"type":"integer","description":"Physical stock."},"reserved_quantity":{"type":"integer","description":"Committed to orders not yet shipped."},"available_quantity":{"type":"integer","description":"What you can still sell. Quote against this."}},"example":{"on_hand_quantity":40,"reserved_quantity":12,"available_quantity":28}},"SkuUpsertRequest":{"type":"object","required":["sku_id"],"properties":{"sku_id":{"type":"string","description":"Your identifier. Stable — this is what order `items` reference."},"name":{"type":["string","null"]},"weight":{"type":["number","string","null"],"description":"Pounds."},"barcode":{"type":["string","null"]},"width":{"type":["number","string","null"],"description":"Inches."},"length":{"type":["number","string","null"],"description":"Inches."},"height":{"type":["number","string","null"],"description":"Inches."},"internal_notes":{"type":"string","x-status":"coming","description":"Coming: your own notes. Sending it today changes nothing."},"perishable":{"type":"boolean","x-status":"coming","description":"Coming: whether the SKU is perishable. Sending it today changes nothing."},"images":{"type":"array","items":{"type":"string"},"x-status":"coming","description":"Coming: image URLs. Sending them today changes nothing."}},"example":{"sku_id":"s_ribeye_12oz","name":"Ribeye 12oz","weight":0.75,"barcode":"0123456789012","width":2.0,"length":1.0,"height":3.0}},"Order":{"type":"object","additionalProperties":false,"required":["fc","order_id","customer_order_id","scheduled_fulfillment_date","can_modify","cancelled_at","shipping_method","packaging","items","shipping_address","sender_name","gift_message","billing_address","shipments"],"properties":{"fc":{"$ref":"#/components/schemas/FcCode"},"order_id":{"type":["string","null"],"description":"Your own internal identifier."},"customer_order_id":{"type":["string","null"],"description":"The number your customer sees."},"scheduled_fulfillment_date":{"type":["string","null"],"format":"date"},"can_modify":{"type":"boolean","description":"Whether this order can still be changed through the API. `false`\nonce it is picked, packed, shipped or cancelled.\n"},"cancelled_at":{"type":["string","null"],"format":"date-time"},"returned_at":{"type":["string","null"],"format":"date-time","description":"When the warehouse recorded that the carrier brought this shipped order's\nbox back. Null unless it came back.\n"},"return_reason":{"type":["string","null"],"enum":["recalled","undeliverable","refused","damaged","other",null],"description":"Why it came back, as the warehouse recorded it: `recalled` (the sender\nrecalled it), `undeliverable` (couldn't be delivered to the address),\n`refused` (the customer refused it), `damaged` (damaged in transit) or\n`other` (other, or not marked).\n"},"shipping_method":{"$ref":"#/components/schemas/OrderShippingMethod"},"packaging":{"$ref":"#/components/schemas/OrderPackaging"},"items":{"type":"array","description":"Your own products only — Crowd Cow packing inserts are excluded. A\ncancelled order lists what it held when it was cancelled; one\ncancelled before 23 October 2026 lists none.\n","items":{"$ref":"#/components/schemas/OrderItem"}},"shipping_address":{"$ref":"#/components/schemas/ShippingAddress"},"sender_name":{"type":["string","null"]},"gift_message":{"type":["string","null"]},"billing_address":{"$ref":"#/components/schemas/BillingAddress"},"shipments":{"type":"array","description":"Shipped shipments only. An order that has not shipped has an empty\narray, not a placeholder.\n","items":{"$ref":"#/components/schemas/OrderShipment"}},"updated_at":{"type":"string","x-status":"coming","description":"Coming: RFC 3339 UTC. Changes whenever anything on the order does.\nOrders do not carry it yet, so it is absent rather than required.\n"}},"example":{"fc":"dunmore","order_id":"1234567","customer_order_id":"456","scheduled_fulfillment_date":"2026-06-02","can_modify":true,"cancelled_at":null,"shipping_method":{"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS"},"packaging":{"dry_ice_in_pounds":10},"items":[{"sku":"s_ribeye_12oz","quantity":2,"weight":"12 oz"},{"sku":"s_bison_burger_8pk","quantity":1,"weight":4.0}],"shipping_address":{"name":"John Doe","address_1":"123 Main St","address_2":"Apt 1","city":"Kirkland","state":"WA","postal_code":"98033","phone_number":"(206) 555-1212","delivery_instructions":"Gate code 4432"},"sender_name":"Jane Roe","gift_message":"Happy birthday, John!","billing_address":{"address_1":"123 Billing St","address_2":"Suite 500","city":"Seattle","state":"WA","postal_code":"98104"},"shipments":[{"tracking_number":"1Z999AA10123456784","carrier":"UPS","service":"Ground","tracking_url":"https://www.ups.com/track?tracknum=1Z999AA10123456784","anticipated_delivery_date":"2026-06-04","delivered_at":"2026-06-04T19:47:00Z","shipped_at":"2026-06-02T01:31:00Z"}],"updated_at":"2026-06-03T14:02:11Z"}},"OrderShippingMethod":{"type":"object","additionalProperties":false,"required":["carrier","service","shipping_option_name"],"properties":{"carrier":{"type":["string","null"],"description":"The carrier, e.g. `UPS`, `FedEx`, `OnTracV3`, `Jitsu`."},"service":{"type":["string","null"],"description":"The carrier's code for the service on the label once it ships, and\nplanned until then, e.g. `Ground`, `2ndDayAir`, `FEDEX_GROUND`,\n`GROUND_HOME_DELIVERY`. FedEx ground ships `GROUND_HOME_DELIVERY` to\na home and `FEDEX_GROUND` to a business.\n"},"shipping_option_name":{"type":["string","null"],"description":"The option the order ships on, the name you pick it by. It stays\nthe same whichever service the carrier uses for the address:\nFedEx ground is `FedEx Ground` to a home or a business.\n"}},"example":{"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS"}},"OrderPackaging":{"type":"object","additionalProperties":false,"required":["dry_ice_in_pounds"],"properties":{"dry_ice_in_pounds":{"type":"integer","description":"Whole pounds of dry ice we plan to pack in each box of the order:\nour estimate plus any extra ice you add to every order.\n"}},"example":{"dry_ice_in_pounds":10}},"OrderItem":{"type":"object","additionalProperties":false,"required":["sku","quantity","weight"],"properties":{"sku":{"type":["string","null"]},"quantity":{"type":["integer","null"],"description":"What was originally ordered, not what was picked."},"weight":{"type":["number","string","null"],"description":"Pounds as a number, unless the SKU opts into grams or ounces, in\nwhich case it is a labeled string such as `\"8 oz\"`.\n"}},"example":{"sku":"s_ribeye_12oz","quantity":2,"weight":"12 oz"}},"ShippingAddress":{"type":"object","additionalProperties":false,"required":["name","address_1","address_2","city","state","postal_code","phone_number","delivery_instructions"],"properties":{"name":{"type":["string","null"]},"address_1":{"type":["string","null"]},"address_2":{"type":["string","null"]},"city":{"type":["string","null"]},"state":{"type":["string","null"]},"postal_code":{"type":["string","null"]},"phone_number":{"type":["string","null"]},"delivery_instructions":{"type":["string","null"]}},"example":{"name":"John Doe","address_1":"123 Main St","address_2":"Apt 1","city":"Kirkland","state":"WA","postal_code":"98033","phone_number":"(206) 555-1212","delivery_instructions":"Gate code 4432"}},"BillingAddress":{"type":"object","additionalProperties":false,"required":["address_1","address_2","city","state","postal_code"],"properties":{"address_1":{"type":["string","null"]},"address_2":{"type":["string","null"]},"city":{"type":["string","null"]},"state":{"type":["string","null"]},"postal_code":{"type":["string","null"]}},"example":{"address_1":"123 Billing St","address_2":"Suite 500","city":"Seattle","state":"WA","postal_code":"98104"}},"OrderShipment":{"type":"object","additionalProperties":false,"required":["tracking_number","carrier","service","tracking_url","anticipated_delivery_date","delivered_at","shipped_at"],"properties":{"tracking_number":{"type":["string","null"]},"carrier":{"type":["string","null"]},"service":{"type":["string","null"]},"tracking_url":{"type":["string","null"]},"anticipated_delivery_date":{"type":["string","null"],"format":"date"},"delivered_at":{"type":["string","null"],"format":"date-time"},"shipped_at":{"type":["string","null"],"format":"date-time"},"dry_ice_packed_lbs":{"type":["string","null"],"description":"Dry ice the packer weighed into this box, in pounds, to two places.\nNull when the box wasn't weighed: not packed yet, or packed without\nthe scale.\n"},"items":{"type":["array","null"],"description":"Everything in this box: each SKU, inserts and notes included, and\nhow many units of it.\n`null` when packing didn't record which box holds what (older\norders, and some multi-box orders); a one-box order always lists\neverything it ordered.\n","items":{"$ref":"#/components/schemas/ShipmentItem"}}},"example":{"tracking_number":"1Z999AA10123456784","carrier":"UPS","service":"Ground","tracking_url":"https://www.ups.com/track?tracknum=1Z999AA10123456784","anticipated_delivery_date":"2026-06-04","delivered_at":"2026-06-04T19:47:00Z","shipped_at":"2026-06-02T01:31:00Z","dry_ice_packed_lbs":"10.40","items":[{"sku":"RIBEYE-12OZ","name":"Ribeye 12oz","quantity":2}]}},"ShipmentItem":{"description":"One SKU in a box.","type":"object","additionalProperties":false,"required":["sku","name","quantity"],"properties":{"sku":{"type":["string","null"],"description":"The SKU, as `items[].sku` on the order names it."},"name":{"type":"string","description":"What it is. Boxes also carry things you didn't order, such as a\nrecipe card, a handwritten note or ice packs, so read the name for\nany SKU your catalog doesn't know.\n"},"quantity":{"type":"integer","description":"Units of it in this box."}}},"OrderUpsertRequest":{"type":"object","required":["order_id","items","shipping_address"],"properties":{"order_id":{"type":"string","description":"Your internal identifier; the key for every other endpoint."},"customer_order_id":{"type":["string","null"]},"items":{"type":"array","items":{"type":"object","required":["sku","quantity"],"properties":{"sku":{"type":"string","description":"A SKU id, or one of your pack codes, which is expanded."},"quantity":{"type":["integer","string"]}}}},"shipping_address":{"$ref":"#/components/schemas/ShippingAddressInput"},"billing_address":{"$ref":"#/components/schemas/BillingAddressInput"},"fc":{"type":["string","null"],"description":"Pin to a fulfillment center. Omit to let Crowd Cow route."},"shipping_method":{"type":["object","null"],"description":"Omitted leaves any existing override alone; a quoted\n`shipping_option_name` ships the order on that option; a carrier\nand service pins the order; explicit nulls clear the override.\n","properties":{"shipping_option_name":{"type":["string","null"],"description":"A `shipping_option_name` from `POST /quote` or\n`rescheduling_options`. Ships on that option, on\n`preferred_arrival_date` if sent, else its soonest date. The\norder remembers it until you send another `shipping_method`.\n"},"carrier":{"type":["string","null"]},"service":{"type":["string","null"]}}},"packaging":{"type":["object","null"],"description":"Send one or the other. Kilograms are converted to pounds and\nrounded before the override is written.\n","properties":{"dry_ice_in_pounds":{"type":["number","string","null"]},"dry_ice_in_kilograms":{"type":["number","string","null"]}}},"sender_name":{"type":["string","null"]},"gift_message":{"type":["string","null"]},"recipient_name":{"type":["string","null"]},"recipient_email":{"type":["string","null"]},"email":{"type":["string","null"]},"preferred_arrival_date":{"type":["string","null"],"description":"The date your checkout promised. Picks the slot, not the service; a\ndate in the past is ignored.\n"},"external_references":{"type":"array","items":{"type":"object","properties":{"system":{"type":["string","null"]},"key":{"type":["string","null"]},"value":{"type":["string","null"]}}}},"source_system":{"type":["string","null"]},"origin_channel":{"type":["string","null"]}},"example":{"order_id":"1234567","customer_order_id":"456","items":[{"sku":"s_ribeye_12oz","quantity":2},{"sku":"s_bison_burger_8pk","quantity":1}],"shipping_address":{"name":"John Doe","address_1":"123 Main St","address_2":"Apt 1","city":"Kirkland","state":"WA","postal_code":"98033","phone_number":"(206) 555-1212","delivery_instructions":"Gate code 4432","country_code":"US"},"billing_address":{"name":"John Doe","address_1":"123 Billing St","address_2":"Suite 500","city":"Seattle","state":"WA","postal_code":"98104","phone_number":"(206) 555-1212"},"fc":"dunmore","shipping_method":{"carrier":"UPS","service":"Ground"},"packaging":{"dry_ice_in_pounds":10},"sender_name":"Jane Roe","gift_message":"Happy birthday, John!","recipient_name":"John Doe","recipient_email":"john.doe@example.com","email":"jane.roe@example.com","preferred_arrival_date":"2026-06-04","external_references":[{"system":"shopify","key":"order_gid","value":"gid://shopify/Order/5512341234567"}],"source_system":"shopify","origin_channel":"web"}},"ShippingAddressInput":{"type":"object","description":"The response carries all eight keys; a request may send any subset.\n`state` and `postal_code` must agree — a mismatch fails the whole\nrequest with `422` and no order is created. Unknown and non-US ZIPs\npass, and the city is not checked.\n","properties":{"name":{"type":["string","null"]},"address_1":{"type":["string","null"]},"address_2":{"type":["string","null"]},"city":{"type":["string","null"]},"state":{"type":["string","null"]},"postal_code":{"type":["string","null"]},"phone_number":{"type":["string","null"]},"delivery_instructions":{"type":["string","null"]},"country_code":{"type":["string","null"],"description":"Read only by the state/ZIP consistency check, which skips non-US\naddresses. `country` is accepted as a synonym and used when\n`country_code` is blank. Neither is stored on the order, and\nneither comes back on the order object.\n"},"country":{"type":["string","null"]}},"example":{"name":"John Doe","address_1":"123 Main St","address_2":"Apt 1","city":"Kirkland","state":"WA","postal_code":"98033","phone_number":"(206) 555-1212","delivery_instructions":"Gate code 4432","country_code":"US","country":"United States"}},"BillingAddressInput":{"type":"object","properties":{"name":{"type":["string","null"]},"address_1":{"type":["string","null"]},"address_2":{"type":["string","null"]},"city":{"type":["string","null"]},"state":{"type":["string","null"]},"postal_code":{"type":["string","null"]},"phone_number":{"type":["string","null"]}},"example":{"name":"John Doe","address_1":"123 Billing St","address_2":"Suite 500","city":"Seattle","state":"WA","postal_code":"98104","phone_number":"(206) 555-1212"}},"OverrideIceRequest":{"type":"object","description":"One of the two keys must be present. An empty value clears the\noverride; omitting both is a `422`.\n","properties":{"dry_ice_in_pounds":{"type":["number","string","null"]},"dry_ice_in_kilograms":{"type":["number","string","null"]}},"example":{"dry_ice_in_pounds":10}},"OverrideShippingMethodRequest":{"type":"object","properties":{"carrier":{"type":["string","null"]},"service":{"type":["string","null"]}},"example":{"carrier":"UPS","service":"Ground"}},"ShippingAddressChange":{"type":"object","description":"Only the fields that change. A field left out keeps the order's\ncurrent value; one sent empty is cleared. `state` and `postal_code`\nmust agree.\n","properties":{"name":{"type":["string","null"]},"address_1":{"type":["string","null"]},"address_2":{"type":["string","null"]},"city":{"type":["string","null"]},"state":{"type":["string","null"]},"postal_code":{"type":["string","null"]},"phone_number":{"type":["string","null"]},"delivery_instructions":{"type":["string","null"]}},"example":{"address_1":"450 Ocean Blvd","address_2":"Apt 3","city":"Long Beach","state":"CA","postal_code":"90815"}},"ShippingAddressOptionsRequest":{"type":"object","required":["shipping_address"],"properties":{"shipping_address":{"$ref":"#/components/schemas/ShippingAddressChange"},"arrival_date_min":{"type":"string","format":"date","description":"Only options arriving on or after this date, `YYYY-MM-DD`. With\nneither date, the window is today through 21 days from today\n(Pacific).\n"},"arrival_date_max":{"type":"string","format":"date","description":"Only options arriving on or before this date, `YYYY-MM-DD`. Pass\nit to see dates past the next 21 days.\n"}},"example":{"shipping_address":{"address_1":"450 Ocean Blvd","city":"Long Beach","state":"CA","postal_code":"90815"}}},"ShippingAddressOptions":{"type":"object","additionalProperties":false,"required":["order_id","current_fc","options"],"properties":{"order_id":{"type":["string","null"]},"current_fc":{"description":"The warehouse the order ships from today.","$ref":"#/components/schemas/FcCode"},"options":{"type":"array","items":{"$ref":"#/components/schemas/ShippingAddressOption"}}},"example":{"order_id":"1234567","current_fc":"dunmore","options":[{"arrival_date":"2026-06-05","fulfillment_date":"2026-06-02","transit_days":3,"carrier":"UPS","service":"Ground","shipping_option_name":"UPS Ground","fc":"watsonville","preferred":true,"changes_fc":true}]}},"ShippingAddressOption":{"type":"object","additionalProperties":false,"required":["arrival_date","fulfillment_date","transit_days","carrier","service","shipping_option_name","fc","preferred","changes_fc"],"properties":{"arrival_date":{"type":["string","null"],"format":"date"},"fulfillment_date":{"type":["string","null"],"format":"date"},"transit_days":{"type":["integer","null"]},"carrier":{"type":["string","null"]},"service":{"type":["string","null"]},"shipping_option_name":{"type":["string","null"]},"fc":{"$ref":"#/components/schemas/FcCode"},"preferred":{"type":"boolean"},"changes_fc":{"type":"boolean","description":"True when the order would ship from a different warehouse than\n`current_fc`.\n"}}},"UpdateShippingAddressRequest":{"type":"object","required":["shipping_address","arrival_date","shipping_option_name","fc"],"properties":{"shipping_address":{"$ref":"#/components/schemas/ShippingAddressChange"},"arrival_date":{"type":"string","format":"date","description":"An option's `arrival_date`, `YYYY-MM-DD`."},"shipping_option_name":{"type":"string","description":"The same option's `shipping_option_name`."},"fc":{"type":"string","description":"The same option's `fc`."}},"example":{"shipping_address":{"address_1":"450 Ocean Blvd","city":"Long Beach","state":"CA","postal_code":"90815"},"arrival_date":"2026-06-05","shipping_option_name":"UPS Ground","fc":"watsonville"}},"GiftMessageRequest":{"type":"object","required":["gift_message"],"properties":{"gift_message":{"type":["string","null"],"description":"The card's new text. Empty removes the card."}},"example":{"gift_message":"Happy birthday, John!"}},"RescheduleRequest":{"type":"object","properties":{"arrival_date":{"type":["string","null"]},"shipping_option_name":{"type":["string","null"]},"carrier":{"type":["string","null"]},"service":{"type":["string","null"]}},"example":{"arrival_date":"2026-06-04","shipping_option_name":"MEM LH UPS","carrier":"UPS","service":"Ground"}},"ReschedulingCurrentPlan":{"description":"The plan the order is on now, in the same fields an option\ncarries, so staying put can be read against moving. It is not always one\nof `options`: once a fulfillment center's cutoff has passed, the date the\norder already holds is no longer a date it could be moved to.\n","type":"object","additionalProperties":false,"required":["arrival_date","fulfillment_date","transit_days","carrier","service","shipping_option_name","fc"],"properties":{"arrival_date":{"type":["string","null"],"format":"date"},"fulfillment_date":{"type":["string","null"],"format":"date"},"transit_days":{"type":["integer","null"]},"carrier":{"type":["string","null"]},"service":{"type":["string","null"]},"shipping_option_name":{"type":["string","null"]},"fc":{"$ref":"#/components/schemas/FcCode"},"cost":{"description":"What this order is billed at your contracted rates on the plan it\nholds now, built exactly like an option's, so the two `total`s\nsubtract to what a move costs. Absent only when this plan cannot\nbe priced right now; `cost_unavailable_reason` then says why.\n","$ref":"#/components/schemas/QuoteCost"},"cost_unavailable_reason":{"$ref":"#/components/schemas/CostUnavailableReason"}},"example":{"arrival_date":"2026-06-04","fulfillment_date":"2026-06-02","transit_days":2,"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fc":"dunmore","cost":{"total":"21.72","currency":"USD","lines":[{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"},{"category":"line_haul","label":"Line haul","quantity":1,"unit_amount":"1.10","amount":"1.10","basis":"contracted_rate"},{"category":"transportation","label":"National carrier (UPS/FedEx/OnTrac)","quantity":1,"unit_amount":"10.27","amount":"10.27","basis":"carrier_quote"},{"category":"packaging","label":"Packaging","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"estimate"},{"category":"coolant_dry_ice","label":"Coolant — dry ice","quantity":10,"unit_amount":"0.3850","amount":"3.85","basis":"estimate"}],"excludes":[]}}},"ReschedulingOptions":{"type":"object","additionalProperties":false,"required":["order_id","current_arrival_date","current_shipping_option_name","options"],"properties":{"order_id":{"type":["string","null"]},"current_arrival_date":{"type":["string","null"],"format":"date"},"current_shipping_option_name":{"type":["string","null"]},"current":{"description":"The whole of the plan the order holds now, `cost` included. `current_arrival_date` and `current_shipping_option_name`\nrepeat two of its fields and are not going anywhere.\n","$ref":"#/components/schemas/ReschedulingCurrentPlan"},"options":{"type":"array","items":{"$ref":"#/components/schemas/ReschedulingPlan"}}},"example":{"order_id":"1234567","current_arrival_date":"2026-06-04","current_shipping_option_name":"MEM LH UPS","current":{"arrival_date":"2026-06-04","fulfillment_date":"2026-06-02","transit_days":2,"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fc":"dunmore","cost":{"total":"21.72","currency":"USD","lines":[{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"},{"category":"line_haul","label":"Line haul","quantity":1,"unit_amount":"1.10","amount":"1.10","basis":"contracted_rate"},{"category":"transportation","label":"National carrier (UPS/FedEx/OnTrac)","quantity":1,"unit_amount":"10.27","amount":"10.27","basis":"carrier_quote"},{"category":"packaging","label":"Packaging","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"estimate"},{"category":"coolant_dry_ice","label":"Coolant — dry ice","quantity":10,"unit_amount":"0.3850","amount":"3.85","basis":"estimate"}],"excludes":[]}},"options":[{"arrival_date":"2026-06-04","fulfillment_date":"2026-06-02","transit_days":2,"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fc":"dunmore","preferred":true,"cost":{"total":"21.72","currency":"USD","lines":[{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"},{"category":"line_haul","label":"Line haul","quantity":1,"unit_amount":"1.10","amount":"1.10","basis":"contracted_rate"},{"category":"transportation","label":"National carrier (UPS/FedEx/OnTrac)","quantity":1,"unit_amount":"10.27","amount":"10.27","basis":"carrier_quote"},{"category":"packaging","label":"Packaging","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"estimate"},{"category":"coolant_dry_ice","label":"Coolant — dry ice","quantity":10,"unit_amount":"0.3850","amount":"3.85","basis":"estimate"}],"excludes":[]}},{"arrival_date":"2026-06-11","fulfillment_date":"2026-06-09","transit_days":2,"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fc":"dallas","preferred":false,"cost":{"total":"24.04","currency":"USD","lines":[{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"},{"category":"line_haul","label":"Line haul","quantity":1,"unit_amount":"1.10","amount":"1.10","basis":"contracted_rate"},{"category":"transportation","label":"National carrier (UPS/FedEx/OnTrac)","quantity":1,"unit_amount":"12.59","amount":"12.59","basis":"carrier_quote"},{"category":"packaging","label":"Packaging","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"estimate"},{"category":"coolant_dry_ice","label":"Coolant — dry ice","quantity":10,"unit_amount":"0.3850","amount":"3.85","basis":"estimate"}],"excludes":[]}}]}},"CostUnavailableReason":{"type":"string","description":"Present exactly when `cost` is absent. `rate_unavailable`: a carrier\nrate lookup failed or timed out, so asking again may price it.\n`unpriceable`: nothing a retry changes prices it, such as a carrier\nwith no price before it ships, or a cart no box holds. `no_rate_card`:\nyour organization has no rate card with us.\n","enum":["rate_unavailable","unpriceable","no_rate_card"]},"ReschedulingPlan":{"type":"object","additionalProperties":false,"required":["arrival_date","fulfillment_date","transit_days","carrier","service","shipping_option_name","fc","preferred"],"properties":{"arrival_date":{"type":["string","null"],"format":"date"},"fulfillment_date":{"type":["string","null"],"format":"date"},"transit_days":{"type":["integer","null"]},"carrier":{"type":["string","null"]},"service":{"type":["string","null"]},"shipping_option_name":{"type":["string","null"]},"fc":{"$ref":"#/components/schemas/FcCode"},"preferred":{"type":"boolean"},"cost":{"description":"What this order would be billed on this plan at your contracted\nrates, built the same way as a quote's, with the order's own\nadditional-pick charge in the same block. Only the shipping lines\ndiffer between options — compare `total` across options, and with\n`current`, to see what a date costs. Absent when the plan cannot be\npriced; the plan is still one the order can move to, and\n`cost_unavailable_reason` says why.\n","$ref":"#/components/schemas/QuoteCost"},"cost_unavailable_reason":{"$ref":"#/components/schemas/CostUnavailableReason"}},"example":{"arrival_date":"2026-06-04","fulfillment_date":"2026-06-02","transit_days":2,"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fc":"dunmore","preferred":true,"cost":{"total":"21.72","currency":"USD","lines":[{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"},{"category":"line_haul","label":"Line haul","quantity":1,"unit_amount":"1.10","amount":"1.10","basis":"contracted_rate"},{"category":"transportation","label":"National carrier (UPS/FedEx/OnTrac)","quantity":1,"unit_amount":"10.27","amount":"10.27","basis":"carrier_quote"},{"category":"packaging","label":"Packaging","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"estimate"},{"category":"coolant_dry_ice","label":"Coolant — dry ice","quantity":10,"unit_amount":"0.3850","amount":"3.85","basis":"estimate"}],"excludes":[]}}},"ShipmentCalendar":{"type":"object","additionalProperties":false,"required":["postal_code","default_plan","plans"],"properties":{"postal_code":{"type":"string","description":"The normalized five-digit code the plans were built for."},"default_plan":{"description":"The first preferred ground plan, or null when none qualifies.","oneOf":[{"$ref":"#/components/schemas/CalendarPlan"},{"type":"null"}]},"plans":{"type":"array","items":{"$ref":"#/components/schemas/CalendarPlan"}}},"example":{"postal_code":"98101","default_plan":{"fulfillment_date":"2026-06-02","arrival_date":"2026-06-04","transit_days":2,"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fc":"dunmore","preferred":true,"tier":"preferred"},"plans":[{"fulfillment_date":"2026-06-02","arrival_date":"2026-06-04","transit_days":2,"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fc":"dunmore","preferred":true,"tier":"preferred"},{"fulfillment_date":"2026-06-02","arrival_date":"2026-06-04","transit_days":2,"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fc":"dunmore","preferred":true,"tier":"expedited"},{"fulfillment_date":"2026-06-03","arrival_date":"2026-06-05","transit_days":2,"carrier":"UPS","service":"Ground","shipping_option_name":"WAT LH UPS","fc":"watsonville","preferred":false,"tier":"standard"},{"fulfillment_date":"2026-06-03","arrival_date":"2026-06-06","transit_days":3,"carrier":"UPS","service":"Ground","shipping_option_name":"DAL LH UPS","fc":"dallas","preferred":false,"tier":"standard"}]}},"CalendarPlan":{"type":"object","additionalProperties":false,"required":["fulfillment_date","arrival_date","transit_days","carrier","service","shipping_option_name","fc","preferred","tier"],"properties":{"fulfillment_date":{"type":"string","format":"date"},"arrival_date":{"type":"string","format":"date"},"transit_days":{"type":["integer","null"]},"carrier":{"type":["string","null"]},"service":{"type":["string","null"]},"shipping_option_name":{"type":["string","null"]},"fc":{"$ref":"#/components/schemas/FcCode"},"preferred":{"type":"boolean"},"tier":{"type":"string","enum":["preferred","standard","expedited"],"description":"A ground plan that also serves the expedited speed appears twice,\nonce per tier.\n"}},"example":{"fulfillment_date":"2026-06-02","arrival_date":"2026-06-04","transit_days":2,"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fc":"dunmore","preferred":true,"tier":"preferred"}},"QuoteRequest":{"type":"object","required":["destination","items"],"properties":{"destination":{"type":"object","required":["postal_code"],"properties":{"postal_code":{"type":"string","description":"A US ZIP code; one that does not exist is a `422`."}}},"items":{"type":"array","items":{"type":"object","required":["sku_id","quantity"],"properties":{"sku_id":{"type":"string","description":"Your own SKU identifier."},"quantity":{"type":["integer","string"],"description":"A positive integer."}}}},"delivery_date_min":{"type":["string","null"],"format":"date","description":"The earliest delivery date to quote. Alone, it quotes that day and\nthe 6 after it.\n"},"delivery_date_max":{"type":["string","null"],"format":"date","description":"The latest delivery date to quote, at most 6 days after\n`delivery_date_min`. Alone, it quotes the 7 days ending then,\nstarting no earlier than the soonest possible delivery.\n"},"all_options":{"type":"boolean","default":false,"description":"`true` lists every shipping lane and delivery date in the range,\neach priced and tagged. `false` returns the recommended option\nalone, which takes one carrier rate lookup rather than several.\nA form-encoded body sends `true` or `false`; anything else is a\n`422`.\n"}},"example":{"destination":{"postal_code":"98101"},"items":[{"sku_id":"s_ribeye_12oz","quantity":2},{"sku_id":"s_bison_burger_8pk","quantity":1}],"delivery_date_min":"2026-06-03","delivery_date_max":"2026-06-05","all_options":true}},"Quote":{"type":"object","additionalProperties":false,"required":["shipments","ground_eligibility","quoted_at","advisory"],"properties":{"shipments":{"type":"array","description":"One entry per fulfillment center used.","items":{"$ref":"#/components/schemas/QuoteShipment"}},"unfulfillable":{"type":"array","description":"Present only when something cannot be quoted.","items":{"$ref":"#/components/schemas/QuoteUnfulfillable"}},"ground_eligibility":{"$ref":"#/components/schemas/GroundEligibility"},"cost":{"description":"What the whole cart costs you on each shipment's `recommended`\noption. `total` is those options' totals plus the charges below\nit; `lines` holds only the charges billed per order rather than per\nshipment (additional pick), because that is the grain the invoice\nbills them at. Choosing other options, add up those instead — the\nper-order lines are the same. Absent when nothing in the cart can\nship.\n","$ref":"#/components/schemas/QuoteCost"},"quoted_at":{"type":"string","format":"date-time"},"advisory":{"type":"string","description":"A sentence about the quote as a whole, written for a person."}},"example":{"shipments":[{"fc":{"code":"dunmore","display":"ships from your area"},"items":[{"sku_id":"s_ribeye_12oz","quantity":2},{"sku_id":"s_bison_burger_8pk","quantity":1}],"options":[{"carrier":"UPS","service":"NextDayAir","shipping_option_name":"MEM LH UPS NDA","fulfillment_date":"2026-06-02","delivery_date":"2026-06-03","transit_days":1,"tags":["fastest"],"cost":{"total":"45.95","currency":"USD","lines":[{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"},{"category":"line_haul","label":"Line haul","quantity":1,"unit_amount":"1.10","amount":"1.10","basis":"contracted_rate"},{"category":"transportation","label":"National carrier (UPS/FedEx/OnTrac)","quantity":1,"unit_amount":"34.50","amount":"34.50","basis":"carrier_quote"},{"category":"packaging","label":"Packaging","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"estimate"},{"category":"coolant_dry_ice","label":"Coolant — dry ice","quantity":10,"unit_amount":"0.3850","amount":"3.85","basis":"estimate"}],"excludes":[]}},{"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fulfillment_date":"2026-06-02","delivery_date":"2026-06-04","transit_days":2,"tags":["recommended","cheapest"],"cost":{"total":"21.72","currency":"USD","lines":[{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"},{"category":"line_haul","label":"Line haul","quantity":1,"unit_amount":"1.10","amount":"1.10","basis":"contracted_rate"},{"category":"transportation","label":"National carrier (UPS/FedEx/OnTrac)","quantity":1,"unit_amount":"10.27","amount":"10.27","basis":"carrier_quote"},{"category":"packaging","label":"Packaging","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"estimate"},{"category":"coolant_dry_ice","label":"Coolant — dry ice","quantity":10,"unit_amount":"0.3850","amount":"3.85","basis":"estimate"}],"excludes":[]}}]}],"cost":{"total":"23.42","currency":"USD","lines":[{"category":"additional_pick","label":"Additional picks","quantity":2,"unit_amount":"0.85","amount":"1.70","basis":"contracted_rate"}],"excludes":[]},"ground_eligibility":{"eligible":true,"nearest_fc":{"code":"dunmore","display":"your nearest fulfillment warehouse"},"blockers":[]},"quoted_at":"2026-06-01T16:00:00Z","advisory":"Estimate at quote time. CC re-routes at order import if inventory has moved."}},"QuoteShipment":{"type":"object","additionalProperties":false,"required":["fc","items","options"],"properties":{"fc":{"$ref":"#/components/schemas/QuoteFc"},"items":{"type":"array","items":{"$ref":"#/components/schemas/QuoteItem"}},"options":{"type":"array","description":"One per shipping lane and delivery date in the range, soonest\nfirst (ties cheaper first), every one priced. Without\n`all_options`, the `recommended` option alone. Never empty.\n","minItems":1,"items":{"$ref":"#/components/schemas/QuoteOption"}},"reason":{"type":"string","description":"Present only when noteworthy: nothing could arrive in your date\nrange, so these are the options for the 7 days from the soonest\ndelivery on or after your `delivery_date_min`, or from the soonest\ndelivery when you sent none\n(`no_delivery_between_\u003cmin\u003e_and_\u003cmax\u003e; next_available_used`),\nor this is a secondary shipment because the primary center was short\n(`primary_fc_lacks_inventory_for_sku:\u003csku_id\u003e`). Both can appear,\njoined by `; `.\n"}},"example":{"fc":{"code":"dunmore","display":"ships from your area"},"items":[{"sku_id":"s_ribeye_12oz","quantity":2},{"sku_id":"s_bison_burger_8pk","quantity":1}],"options":[{"carrier":"UPS","service":"NextDayAir","shipping_option_name":"MEM LH UPS NDA","fulfillment_date":"2026-06-02","delivery_date":"2026-06-03","transit_days":1,"tags":["fastest"],"cost":{"total":"45.95","currency":"USD","lines":[{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"},{"category":"line_haul","label":"Line haul","quantity":1,"unit_amount":"1.10","amount":"1.10","basis":"contracted_rate"},{"category":"transportation","label":"National carrier (UPS/FedEx/OnTrac)","quantity":1,"unit_amount":"34.50","amount":"34.50","basis":"carrier_quote"},{"category":"packaging","label":"Packaging","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"estimate"},{"category":"coolant_dry_ice","label":"Coolant — dry ice","quantity":10,"unit_amount":"0.3850","amount":"3.85","basis":"estimate"}],"excludes":[]}},{"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fulfillment_date":"2026-06-02","delivery_date":"2026-06-04","transit_days":2,"tags":["recommended","cheapest"],"cost":{"total":"21.72","currency":"USD","lines":[{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"},{"category":"line_haul","label":"Line haul","quantity":1,"unit_amount":"1.10","amount":"1.10","basis":"contracted_rate"},{"category":"transportation","label":"National carrier (UPS/FedEx/OnTrac)","quantity":1,"unit_amount":"10.27","amount":"10.27","basis":"carrier_quote"},{"category":"packaging","label":"Packaging","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"estimate"},{"category":"coolant_dry_ice","label":"Coolant — dry ice","quantity":10,"unit_amount":"0.3850","amount":"3.85","basis":"estimate"}],"excludes":[]}}]}},"QuoteOption":{"description":"One way to ship the shipment: a shipping lane and delivery date, with\nwhat it costs you. See `POST /quote` for sending it back on\n`PUT /orders`.\n","type":"object","additionalProperties":false,"required":["carrier","service","shipping_option_name","fulfillment_date","delivery_date","transit_days","tags","cost"],"properties":{"carrier":{"type":["string","null"]},"service":{"type":["string","null"]},"shipping_option_name":{"type":["string","null"],"description":"The stable code for this carrier lane."},"fulfillment_date":{"type":"string","format":"date"},"delivery_date":{"type":"string","format":"date"},"transit_days":{"type":["integer","null"]},"tags":{"type":"array","description":"Each tag is on exactly one option per shipment. `recommended` is\nthe option an order is routed to (cheapest tier, then soonest);\n`cheapest` the lowest\n`cost.total` (ties to the sooner date); `fastest` the soonest\n`delivery_date` (ties to the lower total). `cheapest` and `fastest`\nonly with `all_options`. Usually empty.\n","items":{"type":"string","enum":["recommended","cheapest","fastest"]}},"cost":{"$ref":"#/components/schemas/QuoteCost"}},"example":{"carrier":"UPS","service":"Ground","shipping_option_name":"MEM LH UPS","fulfillment_date":"2026-06-02","delivery_date":"2026-06-04","transit_days":2,"tags":["recommended","cheapest"],"cost":{"total":"21.72","currency":"USD","lines":[{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"},{"category":"line_haul","label":"Line haul","quantity":1,"unit_amount":"1.10","amount":"1.10","basis":"contracted_rate"},{"category":"transportation","label":"National carrier (UPS/FedEx/OnTrac)","quantity":1,"unit_amount":"10.27","amount":"10.27","basis":"carrier_quote"},{"category":"packaging","label":"Packaging","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"estimate"},{"category":"coolant_dry_ice","label":"Coolant — dry ice","quantity":10,"unit_amount":"0.3850","amount":"3.85","basis":"estimate"}],"excludes":[]}}},"QuoteFc":{"type":"object","additionalProperties":false,"required":["code","display"],"properties":{"code":{"$ref":"#/components/schemas/FcCode"},"display":{"type":"string","description":"A customer-safe string that never names the warehouse."}},"example":{"code":"dunmore","display":"ships from your area"}},"QuoteItem":{"type":"object","additionalProperties":false,"required":["sku_id","quantity"],"properties":{"sku_id":{"type":"string"},"quantity":{"type":"integer"}},"example":{"sku_id":"s_ribeye_12oz","quantity":2}},"QuoteUnfulfillable":{"type":"object","additionalProperties":false,"required":["sku_id","quantity_requested","quantity_available_across_all_fcs","reason"],"properties":{"sku_id":{"type":"string"},"quantity_requested":{"type":"integer"},"quantity_available_across_all_fcs":{"type":"integer","description":"Stock at every fulfillment center that ships your orders, including any that cannot reach this destination."},"reason":{"type":"string","description":"`no_eligible_shipment_plan` also covers stock held only at centers that cannot ship to this destination; `insufficient_inventory_across_all_fcs` means the centers that can ship here hold too few.","enum":["no_fc_has_inventory","insufficient_inventory_across_all_fcs","no_shipping_options_available","no_eligible_shipment_plan"]}},"example":{"sku_id":"s_bison_burger_8pk","quantity_requested":1,"quantity_available_across_all_fcs":0,"reason":"no_fc_has_inventory"}},"GroundEligibility":{"type":"object","additionalProperties":false,"required":["eligible","nearest_fc","blockers"],"properties":{"eligible":{"type":"boolean","description":"True only when every item ships and every shipment is a ground\nservice. False with empty `blockers` means air was quoted for\nspeed, not for lack of stock.\n"},"nearest_fc":{"oneOf":[{"$ref":"#/components/schemas/QuoteFc"},{"type":"null"}]},"blockers":{"type":"array","description":"What the nearest ground center is short on.","items":{"$ref":"#/components/schemas/GroundBlocker"}}},"example":{"eligible":true,"nearest_fc":{"code":"dunmore","display":"your nearest fulfillment warehouse"},"blockers":[]}},"GroundBlocker":{"type":"object","additionalProperties":false,"required":["sku_id","requested_quantity","available_quantity","recommended_max_quantity","reason"],"properties":{"sku_id":{"type":"string"},"requested_quantity":{"type":"integer"},"available_quantity":{"type":"integer"},"recommended_max_quantity":{"type":"integer","description":"Reduce to this to keep ground shipping."},"reason":{"type":"string","enum":["insufficient_inventory_at_nearest_fc"]}},"example":{"sku_id":"s_ribeye_12oz","requested_quantity":2,"available_quantity":1,"recommended_max_quantity":1,"reason":"insufficient_inventory_at_nearest_fc"}},"QuoteCost":{"description":"What the option costs you: what Crowd Cow would bill at your\ncontracted rates, plus — when it ships on a carrier account you own —\nthat carrier's rate on your account, which you pay the carrier\ndirectly. Never a carrier list price. Dollars as a decimal string, with\nthe currency beside it. Each shipment is priced as one box; an order\nthat ends up packed in more than one is billed labor and transportation\nfor each box.\n\nIt is an estimate at quote time. A `contracted_rate` line is exact\nunless your rate card changes before the order ships. A\n`carrier_quote` line is the carrier's live rate plus your contracted\nmarkup, for a parcel estimated from the cart — its weight, typical\ncoolant for your recent orders on that service, and the box that holds\nthem — and the figure we bill is the one captured when the label is\nbought. A `historical_estimate` line is AxleHire's (Jitsu's) typical\ncharge for comparable shipments plus your markup, and we bill what\nAxleHire invoices for yours.\n\nAn `estimate` line is packaging, dry ice or a liner. The box, the dry\nice and whether a liner goes in are chosen when the order is packed, so\nwe estimate them from your cart and destination: the box you almost\nalways ship in, or else the smallest box that holds the cart, the dry ice your recent orders on that service carried (or our\nice chart, without that history; for an existing order's rescheduling\noptions, the order's own dry ice on that plan), and our liner rule for the service,\ntransit time and destination. They are priced at the same rates and\nmarkups your invoice uses; what we bill is what actually went in the\nbox.\n\n`excludes` names every charge that is **not** in `total`, so a figure\nis never quietly incomplete. It is usually empty. Transportation is\nnever in it: an option we cannot price is left out rather than shown\nwithout it.\n","type":"object","additionalProperties":false,"required":["total","currency","lines","excludes"],"properties":{"total":{"type":"string","description":"The sum of this block's `lines`, in dollars, to the cent. On a\nquote's own `cost` it also includes every shipment's `total`, so\nthat figure is what the whole cart would be billed: `23.42` in the\n`Quote` example — one shipment at `21.72`, plus the `1.70`\nadditional-pick line billed per order rather than per shipment.\n"},"currency":{"type":"string"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/QuoteCostLine"}},"excludes":{"type":"array","description":"Charges this total does not include: `packaging` or `coolant` when\nwe have no rate entered to price the box or the dry ice with.\n","items":{"type":"string","enum":["packaging","coolant"]}}},"example":{"total":"21.72","currency":"USD","lines":[{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"},{"category":"line_haul","label":"Line haul","quantity":1,"unit_amount":"1.10","amount":"1.10","basis":"contracted_rate"},{"category":"transportation","label":"National carrier (UPS/FedEx/OnTrac)","quantity":1,"unit_amount":"10.27","amount":"10.27","basis":"carrier_quote"},{"category":"packaging","label":"Packaging","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"estimate"},{"category":"coolant_dry_ice","label":"Coolant — dry ice","quantity":10,"unit_amount":"0.3850","amount":"3.85","basis":"estimate"}],"excludes":[]}},"QuoteCostLine":{"description":"One charge, at the same grain and under the same name your invoice\nbills it — so a quote and the invoice that follows it can be read\nagainst each other.\n","type":"object","additionalProperties":false,"required":["category","label","quantity","unit_amount","amount","basis"],"properties":{"category":{"type":"string","enum":["labor","additional_pick","line_haul","transportation","packaging","coolant_dry_ice","liner"],"description":"`labor` is pick and pack, once per shipment. `additional_pick` is\neach unit past the first, once per order, excluding Crowd Cow\npacking inserts. `line_haul` is the flat move to the carrier's hub.\n`transportation` is the last mile. `packaging` is the box,\n`coolant_dry_ice` the dry ice by the pound, and `liner` the\ninsulated carton liner, each estimated (`basis: estimate`).\n"},"label":{"type":"string","description":"The charge as it reads on your invoice."},"quantity":{"type":"number","description":"Units this line is billed for."},"unit_amount":{"type":"string","description":"Dollars per unit, to four decimal places where the rate has them."},"amount":{"type":"string","description":"`quantity` times `unit_amount`, in dollars, to the cent."},"basis":{"type":"string","enum":["contracted_rate","carrier_quote","historical_estimate","your_carrier_account","estimate"],"description":"Where the figure came from. `contracted_rate` is a flat rate off\nyour rate card and is exact. `estimate` is packaging, dry ice or a\nliner, estimated from your cart and destination and priced at your\ncontracted rates and markups; see `QuoteCost`. `carrier_quote` is the carrier's live\nrate for this service and destination plus your contracted markup,\nso it moves with fuel and surcharges. `historical_estimate` is an\n**estimate**: AxleHire (Jitsu) gives no price until it invoices\nus, and it charges every partner the same, so the figure is the\nmedian of what it charged over the last 90 days in your\ndestination's delivery market for a cart of that weight (a new\nmarket uses the median across all markets), plus your contracted\nmarkup. What we bill is the invoiced cost for your own shipment,\nusually within about a dollar of this. `your_carrier_account` is the carrier's\nlive rate on a carrier account you own, with no markup: the\nshipment goes out on your account, so you pay the carrier directly\nand Crowd Cow bills you no transportation for it. It is in `total`\nso that options compare on what they cost you, and it is **not** on\nyour Crowd Cow invoice.\n"}},"example":{"category":"labor","label":"Labor — pick \u0026 pack","quantity":1,"unit_amount":"3.25","amount":"3.25","basis":"contracted_rate"}},"ShipmentList":{"description":"No total, as everywhere else. `/shipments/summary` takes the same filters\nand answers the counting questions.\n","type":"object","additionalProperties":false,"required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Shipment"}},"has_more":{"type":"boolean","description":"Whether a further page exists."},"next_cursor":{"type":["string","null"],"description":"Pass as `cursor` for the next page; `null` on the last one."}},"example":{"data":[{"id":"shp_9f2c1a","order_id":"1234567","customer_order_id":"456","shipment_number":2,"ship_date":"2026-06-02","shipped_at":"2026-06-02T01:31:00Z","recipient":"John Doe","destination":"Kirkland, WA","carrier":"UPS","service":"Ground","tracking_number":"1Z999AA10123456784","tracking_url":"https://www.ups.com/track?tracknum=1Z999AA10123456784","status":"in_transit","last_scan":{"at":"2026-06-03T14:02:00Z","location":"Sparks, NV","message":"Departed facility"},"promised_arrival":"2026-06-04","predicted_arrival":"2026-06-05","delivered_at":null,"days_late":1,"late_reasons":["Carrier delay"],"tracking_alerts":["mechanical_delay"],"write_off":null,"updated_at":"2026-06-03T14:07:22Z"},{"id":"shp_7b41e0","order_id":"1234566","customer_order_id":"455","shipment_number":1,"ship_date":"2026-06-01","shipped_at":"2026-06-01T01:28:00Z","recipient":"Maria Alvarez","destination":"Austin, TX","carrier":"FedEx","service":"FEDEX_2_DAY","tracking_number":"398112345678","tracking_url":"https://www.fedex.com/fedextrack/?trknbr=398112345678","status":"delivered","last_scan":{"at":"2026-06-03T11:42:00Z","location":"Austin, TX","message":"Delivered, front door"},"promised_arrival":"2026-06-03","predicted_arrival":"2026-06-03","delivered_at":"2026-06-03T11:42:00Z","days_late":0,"late_reasons":[],"tracking_alerts":[],"write_off":null,"updated_at":"2026-06-03T11:45:10Z"}],"has_more":true,"next_cursor":"c2hpcG1lbnRzOnNoaXBfZGF0ZTpkZXNjfDIwMjYtMDYtMDF8c2hwXzdiNDFlMA"}},"Shipment":{"description":"One box.","type":"object","additionalProperties":false,"required":["id","order_id","shipment_number","status","days_late","late_reasons","tracking_alerts","updated_at"],"properties":{"id":{"type":"string","description":"Stable for the life of the box. `shipment_number` is an ordinal\nwithin its order and is not an identifier.\n"},"order_id":{"type":"string","description":"Your own identifier for the order where you sent one, our order\nnumber otherwise — the same value the `order_id` filter matches.\n"},"customer_order_id":{"type":["string","null"],"description":"The number your customer sees, where you sent one."},"shipment_number":{"type":"integer","description":"Which box of the order this is, from 1. An order that ships in one\nbox says 1.\n"},"ship_date":{"type":["string","null"],"description":"`YYYY-MM-DD`. The day the box was scheduled to leave."},"shipped_at":{"type":["string","null"],"format":"date-time","description":"When the carrier took it. Null until then."},"recipient":{"type":["string","null"]},"destination":{"type":["string","null"],"description":"City and state, e.g. `Seattle, WA`."},"carrier":{"type":["string","null"],"description":"The carrier that carries the box: `UPS`, `FedEx`, `OnTracV3` or\n`Jitsu`, the vocabulary the `carrier` filter accepts.\n"},"service":{"type":["string","null"],"description":"The carrier's own service code, e.g. `2ndDayAir`."},"tracking_number":{"type":["string","null"]},"tracking_url":{"type":["string","null"],"description":"The carrier's own tracking page for this box."},"status":{"type":"string","enum":["not_shipped","in_transit","out_for_delivery","delivered"],"description":"Delivery stage. A box out for delivery is also in transit; we report\nthe more specific one.\n"},"last_scan":{"description":"The carrier's most recent scan. Null before the first one.","oneOf":[{"type":"object","additionalProperties":false,"properties":{"at":{"type":["string","null"],"format":"date-time"},"location":{"type":["string","null"]},"message":{"type":["string","null"]}}},{"type":"null"}]},"promised_arrival":{"type":["string","null"],"description":"`YYYY-MM-DD`. The date we promised the recipient."},"predicted_arrival":{"type":["string","null"],"description":"`YYYY-MM-DD`. The carrier's own current estimate."},"delivered_at":{"type":["string","null"],"format":"date-time"},"days_late":{"type":"integer","description":"Days past the promise, counting to delivery or to today. `0` for a\nbox written off — a lost box is not late, it is gone.\n"},"late_reasons":{"type":"array","description":"Why it ran late. Empty for a box that is not late.","items":{"type":"string"}},"tracking_alerts":{"type":"array","description":"Problems the carrier has posted against this box.","items":{"type":"string"}},"write_off":{"type":["string","null"],"enum":[null,"lost","destroyed_in_transit"],"description":"Null unless the box was written off."},"updated_at":{"type":"string","format":"date-time","description":"Last write to the box, tracking scans included — the column\n`updated_at_min` filters on.\n"},"dry_ice_packed_lbs":{"type":["string","null"],"description":"Dry ice the packer weighed into this box, in pounds, to two places.\nNull when the box wasn't weighed: not packed yet, or packed without\nthe scale.\n"},"items":{"type":["array","null"],"description":"Everything in this box: each SKU, inserts and notes included, and\nhow many units of it.\n`null` when packing didn't record which box holds what (older\norders, and some multi-box orders); a one-box order always lists\neverything it ordered.\n","items":{"$ref":"#/components/schemas/ShipmentItem"}}},"example":{"id":"shp_9f2c1a","order_id":"1234567","customer_order_id":"456","shipment_number":2,"ship_date":"2026-06-02","shipped_at":"2026-06-02T01:31:00Z","recipient":"John Doe","destination":"Kirkland, WA","carrier":"UPS","service":"Ground","tracking_number":"1Z999AA10123456784","tracking_url":"https://www.ups.com/track?tracknum=1Z999AA10123456784","status":"in_transit","last_scan":{"at":"2026-06-03T14:02:00Z","location":"Sparks, NV","message":"Departed facility"},"promised_arrival":"2026-06-04","predicted_arrival":"2026-06-05","delivered_at":null,"days_late":1,"late_reasons":["Carrier delay"],"tracking_alerts":["mechanical_delay"],"write_off":null,"updated_at":"2026-06-03T14:07:22Z","dry_ice_packed_lbs":"10.40"}},"ShipmentSummary":{"type":"object","additionalProperties":false,"required":["total","late","tracking_alert","never_arriving","carriers"],"properties":{"total":{"type":"integer"},"late":{"type":"integer","description":"Past the promised date, and not written off."},"tracking_alert":{"type":"integer","description":"Carrying at least one problem scan."},"never_arriving":{"type":"integer","description":"Lost or destroyed in transit."},"carriers":{"type":"array","description":"Every carrier you have shipped with — the whole vocabulary of the\n`carrier` filter, not the carriers in the filtered set.\n","items":{"type":"string"}}},"example":{"total":1284,"late":47,"tracking_alert":23,"never_arriving":3,"carriers":["UPS","FedEx","OnTracV3","Jitsu"]}},"DeliveryPerformance":{"type":"object","additionalProperties":false,"required":["period","tiles","weeks","findings","order_status_counts"],"properties":{"period":{"$ref":"#/components/schemas/ReportPeriod"},"tiles":{"$ref":"#/components/schemas/DeliveryTiles"},"weeks":{"type":"array","description":"Up to thirteen weeks, newest first, ending with the week your first\nshipment went out in. An empty week between two busy ones is real\nhistory and stays.\n","items":{"allOf":[{"$ref":"#/components/schemas/ReportPeriod"},{"$ref":"#/components/schemas/DeliveryFigures"}]}},"findings":{"type":"array","description":"What is worth your attention this period, by rule rather than by\njudgement. Empty when there is nothing to say.\n","items":{"$ref":"#/components/schemas/DeliveryFinding"}},"order_status_counts":{"type":"array","description":"Your orders in the period, by where they got to.","items":{"$ref":"#/components/schemas/OrderStatusCount"}}},"example":{"period":{"period_start":"2026-05-31","period_end":"2026-06-06"},"tiles":{"shipments":{"current":432,"prior":418},"on_time_rate":{"current":0.9438,"prior":0.9601},"late":{"current":24,"prior":17},"never_arriving":{"current":1,"prior":0},"tracking_alerts":{"current":18,"prior":14},"avg_transit_days":{"current":2.1,"prior":1.98}},"weeks":[{"period_start":"2026-05-31","period_end":"2026-06-06","shipments":432,"on_time_rate":0.9438,"late":24,"never_arriving":1,"tracking_alerts":18,"avg_transit_days":2.1},{"period_start":"2026-05-24","period_end":"2026-05-30","shipments":418,"on_time_rate":0.9601,"late":17,"never_arriving":0,"tracking_alerts":14,"avg_transit_days":1.98},{"period_start":"2026-05-17","period_end":"2026-05-23","shipments":455,"on_time_rate":0.9385,"late":28,"never_arriving":2,"tracking_alerts":21,"avg_transit_days":2.24},{"period_start":"2026-05-10","period_end":"2026-05-16","shipments":401,"on_time_rate":0.9502,"late":20,"never_arriving":0,"tracking_alerts":12,"avg_transit_days":2.05},{"period_start":"2026-05-03","period_end":"2026-05-09","shipments":388,"on_time_rate":0.9455,"late":21,"never_arriving":1,"tracking_alerts":15,"avg_transit_days":2.13},{"period_start":"2026-04-26","period_end":"2026-05-02","shipments":447,"on_time_rate":0.9309,"late":31,"never_arriving":2,"tracking_alerts":26,"avg_transit_days":2.41},{"period_start":"2026-04-19","period_end":"2026-04-25","shipments":410,"on_time_rate":0.9563,"late":18,"never_arriving":0,"tracking_alerts":11,"avg_transit_days":1.94},{"period_start":"2026-04-12","period_end":"2026-04-18","shipments":396,"on_time_rate":0.947,"late":21,"never_arriving":1,"tracking_alerts":16,"avg_transit_days":2.08},{"period_start":"2026-04-05","period_end":"2026-04-11","shipments":429,"on_time_rate":0.9394,"late":26,"never_arriving":0,"tracking_alerts":19,"avg_transit_days":2.19},{"period_start":"2026-03-29","period_end":"2026-04-04","shipments":373,"on_time_rate":0.9518,"late":18,"never_arriving":1,"tracking_alerts":13,"avg_transit_days":2.02},{"period_start":"2026-03-22","period_end":"2026-03-28","shipments":361,"on_time_rate":0.9613,"late":14,"never_arriving":0,"tracking_alerts":9,"avg_transit_days":1.91},{"period_start":"2026-03-15","period_end":"2026-03-21","shipments":344,"on_time_rate":0.9448,"late":19,"never_arriving":1,"tracking_alerts":12,"avg_transit_days":2.16},{"period_start":"2026-03-08","period_end":"2026-03-14","shipments":318,"on_time_rate":0.9367,"late":20,"never_arriving":2,"tracking_alerts":17,"avg_transit_days":2.28}],"findings":[{"message":"24 shipments arrived after the date we promised.","shipments":24,"query":{"late":true}},{"message":"18 shipments are carrying a carrier tracking alert.","shipments":18,"query":{"tracking_alert":true}},{"message":"1 shipment was written off as lost or destroyed.","shipments":1,"query":{"never_arriving":true}}],"order_status_counts":[{"organization":"SEABEAR","cancelled":12,"not_packed":14,"packed":90,"shipped":432,"total":548}]}},"ReportPeriod":{"type":"object","required":["period_start","period_end"],"properties":{"period_start":{"type":"string","description":"`YYYY-MM-DD`, inclusive."},"period_end":{"type":"string","description":"`YYYY-MM-DD`, inclusive."}},"example":{"period_start":"2026-08-01","period_end":"2026-08-31"}},"DeliveryFigures":{"type":"object","required":["shipments","on_time_rate","late","never_arriving","tracking_alerts","avg_transit_days"],"properties":{"shipments":{"type":"integer","description":"Boxes that went out in the period, by ship date."},"on_time_rate":{"type":["number","null"],"description":"On-time ÷ (on-time + late), 0–1. Leaves out boxes still inside their\npromise window and boxes written off. Null until there is something\nto rate.\n"},"late":{"type":"integer"},"never_arriving":{"type":"integer","description":"Lost or destroyed in transit."},"tracking_alerts":{"type":"integer"},"avg_transit_days":{"type":["number","null"],"description":"Mean wall-clock time from carrier handoff to delivery, in days — so a\n1pm-to-9am delivery is 0.8, not 1. Null with nothing delivered.\n"}},"example":{"shipments":432,"on_time_rate":0.9438,"late":24,"never_arriving":1,"tracking_alerts":18,"avg_transit_days":2.1}},"DeliveryTiles":{"description":"Each figure for the period, against the period before it.","type":"object","additionalProperties":false,"required":["shipments","on_time_rate","late","never_arriving","tracking_alerts","avg_transit_days"],"properties":{"shipments":{"$ref":"#/components/schemas/DeliveryTile"},"on_time_rate":{"$ref":"#/components/schemas/DeliveryTile"},"late":{"$ref":"#/components/schemas/DeliveryTile"},"never_arriving":{"$ref":"#/components/schemas/DeliveryTile"},"tracking_alerts":{"$ref":"#/components/schemas/DeliveryTile"},"avg_transit_days":{"$ref":"#/components/schemas/DeliveryTile"}},"example":{"shipments":{"current":432,"prior":418},"on_time_rate":{"current":0.9438,"prior":0.9601},"late":{"current":24,"prior":17},"never_arriving":{"current":1,"prior":0},"tracking_alerts":{"current":18,"prior":14},"avg_transit_days":{"current":2.1,"prior":1.98}}},"DeliveryTile":{"type":"object","additionalProperties":false,"required":["current","prior"],"properties":{"current":{"type":["number","null"]},"prior":{"type":["number","null"],"description":"The same figure for the period of equal length before it."}},"example":{"current":0.9438,"prior":0.9601}},"DeliveryFinding":{"type":"object","additionalProperties":false,"required":["message","shipments","query"],"properties":{"message":{"type":"string","description":"One plain sentence, written for a person to read."},"shipments":{"type":"integer","description":"How many boxes it is about."},"query":{"type":"object","description":"`GET /shipments` filters that list exactly those boxes. Add the\nperiod's dates to scope it to this report.\n","additionalProperties":true}},"example":{"message":"24 shipments arrived after the date we promised.","shipments":24,"query":{"late":true}}},"OrderStatusCount":{"description":"Buckets overlap on purpose: an order cancelled after it shipped counts\nin both, and `not_packed` is what is left of the total once the other\nthree come off.\n","type":"object","additionalProperties":false,"required":["organization","cancelled","not_packed","packed","shipped","total"],"properties":{"organization":{"type":"string","description":"The organization's code, e.g. `SEABEAR`."},"cancelled":{"type":"integer"},"not_packed":{"type":"integer"},"packed":{"type":"integer"},"shipped":{"type":"integer"},"total":{"type":"integer"}},"example":{"organization":"SEABEAR","cancelled":12,"not_packed":14,"packed":90,"shipped":432,"total":548}},"DeliveryFeedbackList":{"type":"object","additionalProperties":false,"required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryFeedback"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"example":{"data":[{"id":"df-8k2m4q7z1x","order_id":"213607","customer_order_id":"#213607","shipment_id":"shp_9f3a2c","submitted_at":"2026-10-21T18:04:12Z","updated_at":"2026-10-21T18:04:12Z","problems_reported":true,"box_condition":["dented"],"frozen_state":"soft","dry_ice_left":"none","liner_condition":["intact"],"items_damaged":false,"damage_note":null,"recommend_score":7,"comments":"Box sat on the porch most of the afternoon.","photo_skip_notes":{},"photos":[{"kind":"outside","url":"https://images.crowdcow.com/delivery_feedback/abc.jpg","ai_damage_seen":"moderate","ai_damage_details":"One corner of the box is crushed."}]}],"has_more":false,"next_cursor":null}},"DeliveryFeedback":{"type":"object","additionalProperties":false,"required":["id","order_id","customer_order_id","shipment_id","submitted_at","updated_at","problems_reported","box_condition","frozen_state","dry_ice_left","liner_condition","items_damaged","damage_note","recommend_score","comments","photo_skip_notes","photos"],"properties":{"id":{"type":"string","description":"This report's identifier. Opaque, and stable."},"order_id":{"type":"string","description":"Your own identifier where you sent one, our order number otherwise."},"customer_order_id":{"type":["string","null"],"description":"The `customer_order_id` you sent with the order."},"shipment_id":{"type":["string","null"],"description":"The `id` of the delivered shipment the report is about, as `/shipments` returns it."},"submitted_at":{"type":"string","format":"date-time","description":"When the customer first sent it, RFC 3339 UTC."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed, RFC 3339 UTC."},"problems_reported":{"type":"boolean","description":"Items damaged, food soft or thawed, or a box or liner that was not\nintact.\n"},"box_condition":{"type":"array","description":"Every answer that applied. `great` means no problem.","items":{"type":"string","enum":["great","dented","torn","wet"]}},"frozen_state":{"type":["string","null"],"enum":["frozen","soft","thawed",null]},"dry_ice_left":{"type":["string","null"],"description":"How much dry ice was left. Reports sent before 12 October 2026 may\ncarry the older size answers `golf_ball`, `baseball` or `chunks`.\n","enum":["none","small_pieces","some_chunks","big_blocks","golf_ball","baseball","chunks",null]},"liner_condition":{"type":"array","description":"Every answer that applied. `intact` means no problem.","items":{"type":"string","enum":["intact","cracked","wet"]}},"items_damaged":{"type":["boolean","null"]},"damage_note":{"type":["string","null"],"description":"What the customer wrote about the damage."},"recommend_score":{"type":["integer","null"],"minimum":0,"maximum":10,"description":"How likely they are to recommend you, 0 to 10."},"comments":{"type":["string","null"]},"photo_skip_notes":{"type":"object","description":"When the customer skipped a photo, what they wrote instead, keyed by\nthe photo's `kind`.\n","additionalProperties":{"type":"string"}},"photos":{"type":"array","items":{"$ref":"#/components/schemas/DeliveryFeedbackPhoto"}}}},"DeliveryFeedbackPhoto":{"type":"object","additionalProperties":false,"required":["kind","url","ai_damage_seen","ai_damage_details"],"properties":{"kind":{"type":"string","enum":["outside","inside","damage"]},"url":{"type":"string","format":"uri","description":"The full-size image. Public; anyone with the link can open it."},"ai_damage_seen":{"type":["string","null"],"description":"Our photo check's read of damage: `none`, `minor` (cosmetic scuffs\nor small dents), `moderate`, `severe` or `cant_tell`. `null` when\nthe photo has not been checked.\n"},"ai_damage_details":{"type":["string","null"],"description":"What the photo check saw."}}},"InventoryTransactionList":{"type":"object","additionalProperties":false,"required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InventoryTransaction"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"example":{"data":[{"id":"inv_4c81a9","occurred_at":"2026-08-14T16:42:11Z","reason":"pick","quantity":-2,"sku_id":"s_ribeye_12oz","sku_name":"Ribeye 12oz","fc":"dunmore","box_id":"B-88213","order_id":"1234567","lot":"L-2026-08-03","owner":"Four Sixes"},{"id":"inv_7b30d2","occurred_at":"2026-08-12T21:05:47Z","reason":"delivery","quantity":-1,"sku_id":"s_bison_burger_8pk","sku_name":"Bison Burger 8pk","fc":"watsonville","box_id":"B-88214","order_id":"1234568","lot":"L-2026-08-03","owner":"Four Sixes"}],"has_more":true,"next_cursor":"eyJ2IjoiMjAyNi0wOC0xNCJ9"}},"InventoryTransaction":{"type":"object","additionalProperties":false,"required":["id","occurred_at","reason","quantity","sku_id","sku_name","fc","box_id","order_id","lot","owner"],"properties":{"id":{"type":"string","description":"This movement's identifier. Opaque, and stable."},"occurred_at":{"type":"string","format":"date-time","description":"When we recorded it, RFC 3339 UTC."},"reason":{"type":"string","description":"The five summary buckets, plus the movements that have none of their\nown. `delivery` is cases arriving at our warehouse and going into\nstock, never a box delivered to a customer; `receiving` is a\ncorrection to a receipt. `fc_transfer` is stock moving between our centres,\n`b2b_order` is a whole case leaving on a wholesale (B2B) order, `lost`\nis picked units of a cancelled order that we could not find to put\nback, and `initial` is the opening balance from when a box entered the system.\n","enum":["receiving","pick","cycle_count","sad_cow","delivery","fc_transfer","pack_order","b2b_order","lost","initial"]},"quantity":{"type":"integer","description":"Signed, in units — negative for anything leaving. A movement of zero\nis not a movement and is never listed.\n"},"sku_id":{"type":["string","null"],"description":"Your own SKU identifier."},"sku_name":{"type":["string","null"]},"fc":{"$ref":"#/components/schemas/FcCode"},"box_id":{"type":["string","null"],"description":"The physical case the units came out of or went into."},"order_id":{"type":["string","null"],"description":"Your own `order_id`, on a movement caused by an order."},"lot":{"type":["string","null"],"description":"The lot the case belongs to, when it has one."},"owner":{"type":"string","description":"Who paid for these units. Under the default every row is your own\nvendor; under `owner=all` this is what separates yours from ours.\n"}},"example":{"id":"inv_4c81a9","occurred_at":"2026-08-14T16:42:11Z","reason":"pick","quantity":-2,"sku_id":"s_ribeye_12oz","sku_name":"Ribeye 12oz","fc":"dunmore","box_id":"B-88213","order_id":"1234567","lot":"L-2026-08-03","owner":"Four Sixes"}},"MonthEndInventoryReport":{"type":"object","additionalProperties":false,"required":["as_of","fcs","lines","total_units"],"properties":{"as_of":{"type":"string","format":"date-time","description":"The last moment counted. The end of the requested month, or now if\nthat month is still running.\n"},"fcs":{"type":"array","description":"The fulfillment centres holding your stock at the cutoff, in a\nstable order — read from the data, since FCs open, close and move.\n","items":{"$ref":"#/components/schemas/ReportFc"}},"lines":{"type":"array","description":"One per SKU with stock somewhere, by SKU name. A SKU sitting at zero\nat every FC is omitted.\n","items":{"$ref":"#/components/schemas/ReportLine"}},"total_units":{"type":"integer"}},"example":{"as_of":"2026-08-31T23:59:59Z","fcs":[{"code":"dunmore","name":"Scranton"},{"code":"dallas","name":"Dallas"},{"code":"watsonville","name":"Watsonville"}],"lines":[{"sku_id":"s_bison_burger_8pk","sku_name":"Bison Burger 8pk","units_by_fc":{"dunmore":120,"dallas":64,"watsonville":0},"total_units":184},{"sku_id":"s_ribeye_12oz","sku_name":"Ribeye 12oz","units_by_fc":{"dunmore":40,"dallas":0,"watsonville":15},"total_units":55}],"total_units":239}},"SadCowReport":{"type":"object","additionalProperties":false,"required":["period","fcs","lines","units_by_reason","total_units"],"properties":{"period":{"allOf":[{"$ref":"#/components/schemas/ReportPeriod"},{"type":"object","required":["in_progress"],"properties":{"in_progress":{"type":"boolean","description":"True when the range runs to today, so the figures can still\nmove. A closed period is final.\n"}}}]},"fcs":{"type":"array","description":"The fulfillment centres that wrote anything off in range.","items":{"$ref":"#/components/schemas/ReportFc"}},"lines":{"type":"array","description":"One per SKU written off in range, by SKU name. Units are positive\nmagnitudes — units lost, not signed movement.\n","items":{"$ref":"#/components/schemas/ReportLine"}},"units_by_reason":{"type":"object","description":"Why the units were written off, as recorded by the warehouse,\nlargest first. Anything unattributed groups as `Not recorded`.\n","additionalProperties":{"type":"integer"},"examples":[{"Damaged Packaging":7,"Discolored":2}]},"total_units":{"type":"integer"}},"example":{"period":{"period_start":"2026-08-01","period_end":"2026-08-31","in_progress":false},"fcs":[{"code":"dunmore","name":"Scranton"},{"code":"dallas","name":"Dallas"},{"code":"watsonville","name":"Watsonville"}],"lines":[{"sku_id":"s_ribeye_12oz","sku_name":"Ribeye 12oz","units_by_fc":{"dunmore":5,"dallas":0,"watsonville":2},"total_units":7},{"sku_id":"s_bison_burger_8pk","sku_name":"Bison Burger 8pk","units_by_fc":{"dunmore":2,"dallas":1,"watsonville":0},"total_units":3}],"units_by_reason":{"Damaged Packaging":6,"Discolored":3,"Not recorded":1},"total_units":10}},"ReportFc":{"type":"object","additionalProperties":false,"required":["code","name"],"properties":{"code":{"$ref":"#/components/schemas/FcCode"},"name":{"type":["string","null"],"description":"What a person reads on the report, e.g. `Scranton`."}},"example":{"code":"dunmore","name":"Scranton"}},"ReportLine":{"type":"object","additionalProperties":false,"required":["sku_id","sku_name","units_by_fc","total_units"],"properties":{"sku_id":{"type":["string","null"]},"sku_name":{"type":["string","null"]},"units_by_fc":{"type":"object","description":"Units at each of the report's `fcs`, keyed by code. Every code in\n`fcs` is present, zero included, so a row is a complete row.\n","additionalProperties":{"type":"integer"}},"total_units":{"type":"integer"}},"example":{"sku_id":"s_ribeye_12oz","sku_name":"Ribeye 12oz","units_by_fc":{"dunmore":40,"dallas":0,"watsonville":15},"total_units":55}},"InventoryTransactionSummary":{"type":"object","additionalProperties":false,"required":["lines","total"],"properties":{"lines":{"type":"array","description":"One per SKU with a movement in range, by SKU name.","items":{"$ref":"#/components/schemas/InventoryTransactionSummaryLine"}},"total":{"type":"integer","description":"The net of every line — what the filtered range did to the stock on\nhand, in units.\n"}},"example":{"lines":[{"sku_id":"s_ribeye_12oz","sku_name":"Ribeye 12oz","receiving":-6,"pick":-84,"cycle_count":-2,"sad_cow":-3,"delivery":120,"total":23},{"sku_id":"s_bison_burger_8pk","sku_name":"Bison Burger 8pk","receiving":-2,"pick":-22,"cycle_count":1,"sad_cow":-1,"delivery":60,"total":36}],"total":59}},"InventoryTransactionSummaryLine":{"type":"object","additionalProperties":false,"required":["sku_id","sku_name","receiving","pick","cycle_count","sad_cow","delivery","total"],"properties":{"sku_id":{"type":["string","null"]},"sku_name":{"type":["string","null"]},"receiving":{"type":"integer","description":"Corrections to a receipt, either sign."},"pick":{"type":"integer"},"cycle_count":{"type":"integer"},"sad_cow":{"type":"integer"},"delivery":{"type":"integer","description":"Units arriving at our warehouse and put into stock. Positive; boxes\ndelivered to your customers leave stock as `pick`.\n"},"total":{"type":"integer","description":"Every movement on this SKU, including the kinds with no column of\ntheir own, so the columns need not add up to it.\n"}},"example":{"sku_id":"s_ribeye_12oz","sku_name":"Ribeye 12oz","receiving":-6,"pick":-84,"cycle_count":-2,"sad_cow":-3,"delivery":120,"total":23}},"InvoiceList":{"type":"object","additionalProperties":false,"required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"example":{"data":[{"id":"10421","period_start":"2026-08-01","period_end":"2026-08-31","doc_date":"2026-09-02","due_on":"2026-10-02","total":"18422.75","currency":"USD","status":"sent","emailed_at":"2026-09-02T16:04:00Z"},{"id":"10388","period_start":"2026-07-01","period_end":"2026-07-31","doc_date":"2026-08-03","due_on":"2026-09-02","total":"16903.12","currency":"USD","status":"exported","emailed_at":"2026-08-03T15:51:00Z"}],"has_more":true,"next_cursor":"eyJpZCI6IjEwMzg4Iiwic2VlbiI6MzR9"}},"Invoice":{"type":"object","additionalProperties":false,"required":["id","period_start","period_end","doc_date","due_on","total","currency","status","emailed_at"],"properties":{"id":{"type":"string","description":"The invoice number, as printed on the document. Pass it back as\n`{id}` on every other invoice endpoint.\n"},"period_start":{"type":"string","format":"date","description":"First day of the billing period."},"period_end":{"type":"string","format":"date","description":"Last day of the billing period, inclusive."},"doc_date":{"type":["string","null"],"format":"date","description":"The date on the document itself."},"due_on":{"type":["string","null"],"format":"date"},"total":{"type":"string","description":"The document total in dollars, to the cent."},"currency":{"type":"string"},"status":{"type":"string","enum":["sent","exported"],"description":"`exported` means it also reached our accounting system. Only these\ntwo are partner-facing; a draft or voided invoice is not listed.\n"},"emailed_at":{"type":["string","null"],"format":"date-time","description":"When we emailed it to you. Null means it was finalized but never\nsent, which is also when the document endpoints have no file.\n"}},"example":{"id":"10421","period_start":"2026-08-01","period_end":"2026-08-31","doc_date":"2026-09-02","due_on":"2026-10-02","total":"18422.75","currency":"USD","status":"sent","emailed_at":"2026-09-02T16:04:00Z"}},"InvoiceDetail":{"type":"object","additionalProperties":false,"required":["id","period_start","period_end","doc_date","due_on","total","currency","status","emailed_at","line_items","bill_to","ship_to","documents"],"properties":{"id":{"type":"string"},"period_start":{"type":"string","format":"date"},"period_end":{"type":"string","format":"date"},"doc_date":{"type":["string","null"],"format":"date"},"due_on":{"type":["string","null"],"format":"date"},"total":{"type":"string"},"currency":{"type":"string"},"status":{"type":"string","enum":["sent","exported"]},"emailed_at":{"type":["string","null"],"format":"date-time"},"line_items":{"type":"array","description":"The Summary rows, in the order the document prints them.","items":{"$ref":"#/components/schemas/InvoiceLineItem"}},"bill_to":{"type":"array","description":"The billing address, one entry per printed line.","items":{"type":"string"}},"ship_to":{"type":"array","items":{"type":"string"}},"documents":{"$ref":"#/components/schemas/InvoiceDocuments"}},"example":{"id":"10421","period_start":"2026-08-01","period_end":"2026-08-31","doc_date":"2026-09-02","due_on":"2026-10-02","total":"18422.75","currency":"USD","status":"sent","emailed_at":"2026-09-02T16:04:00Z","line_items":[{"no":1,"category":"Fulfillment","item_label":"Order Handling","description":"Pick, pack and label — first item of each order included","quantity":"3142","uom":"orders","unit_cost":"1.7500","extended_cost":"5498.50","adder_pct":"0.00","unit_price":"1.7500","extended_price":"5498.50"},{"no":2,"category":"Fulfillment","item_label":"Item Labor","description":"Additional items picked beyond the first on each order","quantity":"4918","uom":"items","unit_cost":"0.3500","extended_cost":"1721.30","adder_pct":"0.00","unit_price":"0.3500","extended_price":"1721.30"},{"no":3,"category":"Packaging","item_label":"Corrugate and Liners","description":"Insulated shipper, liner and void fill","quantity":"3142","uom":"boxes","unit_cost":"2.1000","extended_cost":"6598.20","adder_pct":"0.00","unit_price":"2.1000","extended_price":"6598.20"},{"no":4,"category":"Packaging","item_label":"Dry Ice","description":"Coolant charged on weight loaded at the fulfillment center","quantity":"16329.21","uom":"lbs","unit_cost":"0.1900","extended_cost":"3102.55","adder_pct":"0.00","unit_price":"0.1900","extended_price":"3102.55"},{"no":5,"category":"Storage","item_label":"Pallet Storage","description":"Average pallet positions held across the period","quantity":"412","uom":"pallet-months","unit_cost":"0.8500","extended_cost":"350.20","adder_pct":"0.00","unit_price":"0.8500","extended_price":"350.20"},{"no":6,"category":"Receiving","item_label":"Inbound Receiving","description":"Pallets received, verified and putaway","quantity":"96","uom":"pallets","unit_cost":"12.0000","extended_cost":"1152.00","adder_pct":"0.00","unit_price":"12.0000","extended_price":"1152.00"}],"bill_to":["Four Sixes Ranch","Attn: Accounts Payable","6666 Ranch Road","Guthrie, TX 79236","United States"],"ship_to":["Crowd Cow Fulfillment — Dunmore","1000 Keystone Industrial Park","Dunmore, PA 18512","United States"],"documents":{"pdf":"https://3pl.crowdcow.com/api/v1/invoices/10421/pdf","detail_xlsx":"https://3pl.crowdcow.com/api/v1/invoices/10421/detail.xlsx"}}},"InvoiceLineItem":{"type":"object","additionalProperties":false,"required":["no","category","item_label","description","quantity","uom","unit_cost","extended_cost","adder_pct","unit_price","extended_price"],"properties":{"no":{"type":"integer","description":"The line's position on the document, from 1."},"category":{"type":"string","description":"Machine-readable service key, e.g. `labor`, `coolant_dry_ice`,\n`marketplace_product`. Stable across periods, so it is what to\nkey on; `item_label` is the human text and may be reworded.\n"},"item_label":{"type":"string","description":"The service name as printed."},"description":{"type":"string"},"quantity":{"type":["string","null"]},"uom":{"type":["string","null"],"description":"The unit the quantity counts — cases, pounds, orders."},"unit_cost":{"type":["string","null"],"description":"Rate per unit, to four decimal places, because a per-unit rate\nrounded to the cent does not reproduce the extended amount.\n\n`marketplace_product` is billed off a price list rather than a\ndisclosed cost plus an adder, so on those lines this is the\nbilled unit price (equal to `unit_price`) — Crowd Cow's product\ncost is proprietary and appears in no field.\n"},"extended_cost":{"type":["string","null"],"description":"`quantity` × `unit_cost`. Equal to `extended_price` on\n`marketplace_product` lines, per `unit_cost` above.\n"},"adder_pct":{"type":["string","null"],"description":"Markup applied to `extended_cost` to reach `extended_price`.\nAlways `'0.00'` on `marketplace_product` lines, so\n`extended_price == extended_cost × (1 + adder_pct / 100)` holds\non every line and needs no special case.\n"},"unit_price":{"type":["string","null"]},"extended_price":{"type":["string","null"]}},"example":{"no":4,"category":"coolant_dry_ice","item_label":"Dry Ice","description":"Coolant charged on weight loaded at the fulfillment center","quantity":"16329.21","uom":"lbs","unit_cost":"0.1900","extended_cost":"3102.55","adder_pct":"0.00","unit_price":"0.1900","extended_price":"3102.55"}},"InvoiceDocuments":{"type":"object","additionalProperties":false,"required":["pdf","detail_xlsx"],"properties":{"pdf":{"type":"string","description":"Where to get the invoice PDF. Redirects to the stored file."},"detail_xlsx":{"type":"string","description":"Where to get the detail workbook. Redirects to the stored file."}},"example":{"pdf":"https://3pl.crowdcow.com/api/v1/invoices/10421/pdf","detail_xlsx":"https://3pl.crowdcow.com/api/v1/invoices/10421/detail.xlsx"}},"OrderCostList":{"type":"object","additionalProperties":false,"required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceOrderCost"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"example":{"data":[{"order_id":"1234567","internal_order_id":"FS-2026-08-114532","order_token":"ord_9f2c1ab84e","delivery_city":"Portland","delivery_state":"OR","delivery_zip":"97214","item_count":6,"net_item_count":5,"item_rate":"0.3500","extended_item_labor":"1.75","marketplace_units":2,"marketplace_price":"23.10"},{"order_id":"1234568","internal_order_id":"FS-2026-08-114533","order_token":"ord_4b71e0c592","delivery_city":"Austin","delivery_state":"TX","delivery_zip":"78704","item_count":3,"net_item_count":2,"item_rate":"0.3500","extended_item_labor":"0.70","marketplace_units":null,"marketplace_price":null}],"has_more":true,"next_cursor":"eyJvcmRlcl90b2tlbiI6Im9yZF80YjcxZTBjNTkyIn0"}},"InvoiceOrderCost":{"type":"object","additionalProperties":false,"required":["order_id","internal_order_id","order_token","delivery_city","delivery_state","delivery_zip","item_count","net_item_count","item_rate","extended_item_labor","marketplace_units","marketplace_price"],"properties":{"order_id":{"type":["string","null"],"description":"Your own order identifier — the number your customer sees, falling\nback to your internal one.\n"},"internal_order_id":{"type":["string","null"]},"order_token":{"type":["string","null"],"description":"Our identifier for the order, as `GET /orders/{id}` reports it."},"delivery_city":{"type":["string","null"]},"delivery_state":{"type":["string","null"]},"delivery_zip":{"type":["string","null"]},"item_count":{"type":"integer","description":"Items picked for the order."},"net_item_count":{"type":"integer","description":"What was billed: the first item of an order is covered by the\norder-level charge, so this is one fewer.\n"},"item_rate":{"type":["string","null"]},"extended_item_labor":{"type":["string","null"]},"marketplace_units":{"type":["integer","null"],"description":"Units of Crowd Cow product on this order that you resold on your own\nstorefront and were billed for. Null when the order carried none.\n"},"marketplace_price":{"type":["string","null"],"description":"What we billed you for those units, in dollars. Null when the order\ncarried none.\n"}},"example":{"order_id":"1234567","internal_order_id":"FS-2026-08-114532","order_token":"ord_9f2c1ab84e","delivery_city":"Portland","delivery_state":"OR","delivery_zip":"97214","item_count":6,"net_item_count":5,"item_rate":"0.3500","extended_item_labor":"1.75","marketplace_units":2,"marketplace_price":"23.10"}},"ShipmentCostList":{"type":"object","additionalProperties":false,"required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceShipmentCost"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"example":{"data":[{"shipped_on":"2026-08-14","carrier":"UPS","order_id":"1234567","internal_order_id":"FS-2026-08-114532","order_token":"ord_9f2c1ab84e","coolant_lbs":"6.50","dry_ice_packed_lbs":"7.12","delivery_city":"Portland","delivery_state":"OR","delivery_zip":"97214","fc_name":"Watsonville","box_name":"Medium Insulated Shipper (M-2)","shipping_option":"Ground","product_weight":"11.20","shipment_weight":"19.40","dry_ice_rate":"0.1900","dry_ice":"1.24","packaging":"2.10","last_mile":"9.86","line_haul":"3.42","labor":"3.50","gel_packs":"0.00","liner":"1.50","printing":"0.50"},{"shipped_on":"2026-08-15","carrier":"OnTrac","order_id":"1234568","internal_order_id":"FS-2026-08-114533","order_token":"ord_4b71e0c592","coolant_lbs":"4.00","dry_ice_packed_lbs":null,"delivery_city":"Austin","delivery_state":"TX","delivery_zip":"78704","fc_name":"Dallas","box_name":"Small Insulated Shipper (S-1)","shipping_option":"Ground","product_weight":"6.80","shipment_weight":"12.10","dry_ice_rate":"0.1900","dry_ice":"0.76","packaging":"1.65","last_mile":"7.42","line_haul":"2.10","labor":"2.45","gel_packs":"0.00","liner":"0.00","printing":"0.00"}],"has_more":true,"next_cursor":"eyJzaGlwbWVudF9pZCI6ODgxNDIsInNoaXBwZWRfb24iOiIyMDI2LTA4LTE1In0"}},"InvoiceShipmentCost":{"type":"object","additionalProperties":false,"required":["shipped_on","carrier","order_id","internal_order_id","order_token","coolant_lbs","dry_ice_packed_lbs","delivery_city","delivery_state","delivery_zip","fc_name","box_name","shipping_option","product_weight","shipment_weight","dry_ice_rate","dry_ice","packaging","last_mile","line_haul","labor","gel_packs","liner","printing"],"properties":{"shipped_on":{"type":["string","null"],"format":"date"},"carrier":{"type":["string","null"]},"order_id":{"type":["string","null"]},"internal_order_id":{"type":["string","null"]},"order_token":{"type":["string","null"]},"coolant_lbs":{"type":["string","null"],"description":"Dry ice requested for the box, in pounds. The dry ice charge bills this amount."},"dry_ice_packed_lbs":{"type":["string","null"],"description":"Dry ice the packer weighed into the box, in pounds. Null when the box wasn't weighed."},"delivery_city":{"type":["string","null"]},"delivery_state":{"type":["string","null"]},"delivery_zip":{"type":["string","null"]},"fc_name":{"type":["string","null"]},"box_name":{"type":["string","null"]},"shipping_option":{"type":["string","null"]},"product_weight":{"type":["string","null"],"description":"Weight of the contents, not counting the box or the dry ice. When we\nweighed the packed box, it is that weight less the empty box and the\ndry ice packed (`dry_ice_packed_lbs`, else the dry ice requested), so\nthe box, `product_weight` and the dry ice add up to `shipment_weight`.\nOtherwise it is our planned contents less the dry ice requested.\n"},"shipment_weight":{"type":["string","null"],"description":"The whole box — box, contents and dry ice — as our scale weighed it\nwhen it was packed, else the weight we planned for it. Null where we\nhave neither; it is not zero.\n"},"dry_ice_rate":{"type":["string","null"],"description":"Rate per pound of dry ice, to four places."},"dry_ice":{"type":["string","null"]},"packaging":{"type":["string","null"]},"last_mile":{"type":["string","null"]},"line_haul":{"type":["string","null"]},"labor":{"type":["string","null"]},"gel_packs":{"type":["string","null"],"description":"Gel packs, billed once per order on its first box."},"liner":{"type":["string","null"],"description":"Shipping liners, billed once per order on its first box."},"printing":{"type":["string","null"],"description":"Packing slips and gift messages printed for this box."}},"example":{"shipped_on":"2026-08-14","carrier":"UPS","order_id":"1234567","internal_order_id":"FS-2026-08-114532","order_token":"ord_9f2c1ab84e","coolant_lbs":"6.50","dry_ice_packed_lbs":"7.12","delivery_city":"Portland","delivery_state":"OR","delivery_zip":"97214","fc_name":"Watsonville","box_name":"Medium Insulated Shipper (M-2)","shipping_option":"Ground","product_weight":"11.20","shipment_weight":"19.40","dry_ice_rate":"0.1900","dry_ice":"1.24","packaging":"2.10","last_mile":"9.86","line_haul":"3.42","labor":"3.50","gel_packs":"0.00","liner":"1.50","printing":"0.50"}},"ApiKey":{"description":"**Coming.** One key, without its token. The token exists only in the\nresponse that created it.\n","type":"object","additionalProperties":false,"required":["id","name","prefix","response_format","created_at"],"properties":{"id":{"type":"string","description":"The key's identifier, for `rotate` and `revoke`."},"name":{"type":"string","description":"What you called it. Name a key after the integration that holds it —\nit is the only thing that tells you what breaks when you revoke it.\n"},"prefix":{"type":"string","description":"The first eight characters of the token. Enough to match a key\nagainst a `.env` file or a log line, useless to anyone who steals\nit.\n"},"response_format":{"type":"string","enum":["modern","legacy"],"description":"`modern` is the shape this document describes. `legacy` is the\nolder envelope, kept for integrations written before it. A rotated\nkey copies the shape of the key it replaces — not the shape your\norganization's other keys happen to use — so rotating an\nintegration never moves it onto an envelope it has never parsed. A\nkey created from scratch is `modern`.\n"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":"string","format":"date-time","description":"When a request last authenticated with this key. Absent until one\ndoes — a key with no `last_used_at` has never been used, and one\nwhose `last_used_at` is a month old is safe to revoke.\n"},"expires_at":{"type":"string","format":"date-time","description":"When the key stops working, if it was given an expiry. Absent means\nit does not expire. A rotated key carries the end of its overlap\nwindow here.\n"},"revoked_at":{"type":"string","format":"date-time","description":"Absent unless the key has been revoked."}},"example":{"id":"key_7Qw2mJ4fT1","name":"Warehouse sync","prefix":"3f9ac21d","response_format":"modern","created_at":"2026-03-04T18:22:01Z","last_used_at":"2026-09-10T16:58:12Z"}},"ApiKeyList":{"description":"**Coming.** Your organization's keys, newest first, revoked ones\nincluded so a revocation is auditable rather than a disappearance.\n","type":"object","additionalProperties":false,"required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"example":{"data":[{"id":"key_7Qw2mJ4fT1","name":"Warehouse sync","prefix":"3f9ac21d","response_format":"modern","created_at":"2026-03-04T18:22:01Z","last_used_at":"2026-09-10T16:58:12Z"},{"id":"key_2Hd8bK6nR5","name":"Warehouse sync (rotation)","prefix":"b17e5d0c","response_format":"modern","created_at":"2026-09-10T17:02:44Z"}],"has_more":false,"next_cursor":null}},"CreateApiKeyRequest":{"description":"**Coming.** What to call the new key, and when it should stop working.","type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"pattern":"\\S","description":"Name it after the integration that will hold it, not after the\nperson creating it. It has to contain something other than\nwhitespace.\n"},"expires_at":{"type":"string","format":"date-time","description":"Optional. When the key should stop working, RFC 3339 UTC. Omit for\na key that does not expire. Must be in the future.\n"}},"example":{"name":"Warehouse sync","expires_at":"2027-03-04T00:00:00Z"}},"ApiKeyCreated":{"description":"**Coming.** The new key, with its token. This is the only response that\ncarries the token — store it before you close the page, because we keep\nonly a hash of it and cannot show it to you again.\n","type":"object","additionalProperties":false,"required":["id","name","prefix","response_format","created_at","token"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string"},"response_format":{"type":"string","enum":["modern","legacy"]},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"token":{"type":"string","description":"The credential. Send it as `X-Api-Key`, or as\n`Authorization: Bearer \u003ctoken\u003e`. Shown once.\n"}},"example":{"id":"key_7Qw2mJ4fT1","name":"Warehouse sync","prefix":"3f9ac21d","response_format":"modern","created_at":"2026-03-04T18:22:01Z","expires_at":"2027-03-04T00:00:00Z","token":"3f9ac21d8e4b47f0a1c6d9e2b5748c3f0a6e1d4b9c72f85a3e0d6b1c4f79a2e5"}},"RotateApiKeyRequest":{"description":"**Coming.** How long the key being rotated should keep working while you\ndeploy its successor.\n","type":"object","additionalProperties":false,"properties":{"overlap_hours":{"type":"integer","minimum":0,"maximum":168,"default":24,"description":"Hours the old key stays valid. `0` retires it the moment the new one\nis issued — correct when the key is believed compromised, and an\noutage otherwise. The maximum is a week.\n"},"name":{"type":"string","minLength":1,"maxLength":100,"pattern":"\\S","description":"Optional name for the new key. Defaults to the old key's name, so a\nrotation does not have to be renamed: the two keys carry the same\nname for the length of the overlap window, which is allowed here and\nnowhere else. A name that is only whitespace is a `422`.\n"},"expires_at":{"type":["string","null"],"format":"date-time","description":"Optional. When the successor should stop working, RFC 3339 UTC.\nOmit it and the successor inherits the expiry the rotated key held\n**before this call** — the overlap end now stamped on the\npredecessor is a retirement, not an expiry it passes on. Send\n`null` for a successor that does not expire. Must be in the future.\n"}},"example":{"overlap_hours":24}},"ApiKeyRotated":{"description":"**Coming.** The successor, with its token, and the key it replaces with\nthe end of its overlap window stamped on it. Deploy the new token, then\ncheck `last_used_at` on the old key to confirm nothing is still holding\nit.\n","type":"object","additionalProperties":false,"required":["created","replaced"],"properties":{"created":{"$ref":"#/components/schemas/ApiKeyCreated"},"replaced":{"$ref":"#/components/schemas/ApiKey"}},"example":{"created":{"id":"key_2Hd8bK6nR5","name":"Warehouse sync","prefix":"b17e5d0c","response_format":"modern","created_at":"2026-09-10T17:02:44Z","token":"b17e5d0c4a9f3216e8d70b5c1f62a9e437c0d81b5e9a4f26c3b70e158d42f6a9"},"replaced":{"id":"key_7Qw2mJ4fT1","name":"Warehouse sync","prefix":"3f9ac21d","response_format":"modern","created_at":"2026-03-04T18:22:01Z","last_used_at":"2026-09-10T16:58:12Z","expires_at":"2026-09-11T17:02:44Z"}}}}}}