Partner Portal
3PL API Reference
Push inventory and orders, reschedule, quote shipping and read delivery dates — programmatically. Everything below was checked against the running API rather than transcribed from an older document.
For AI agents
Hand this page to your agent
This reference is served as plain markdown to anything that asks for it — request this URL with an Accept: text/markdown header and you get the whole document back, with no HTML to strip and no API key needed to read it. Paste the prompt below into Claude, ChatGPT, Cursor, or your own agent runtime.
Prompt for your agent
Read the Crowd Cow 3PL API reference before writing any integration code. Fetch https://3pl.crowdcow.com/api-docs with the header "Accept: text/markdown" and you get the whole reference as markdown — no API key is needed to read it. It covers authentication, creating and updating SKUs, creating and updating orders, cancelling, dry-ice and shipping-method overrides, rescheduling, shipping quotes and the delivery calendar. It also lists what the API deliberately cannot do yet, so do not build against anything that is not in it. Base URL: https://www.crowdcow.com/api/fulfillment/v1 Every request needs an X-Api-Key header.
Or fetch it yourself
curl -H 'Accept: text/markdown' https://3pl.crowdcow.com/api-docs
Authentication
Every request needs an X-Api-Key header. Keys are issued per organization — ask your Crowd Cow contact; there is no self-serve key page yet.
X-Api-Key: <YOUR_TOKEN>
A missing or revoked key returns 401. Two endpoints — Quote and Shipping Calendar — additionally require the key to belong to a 3PL organization, and return 403 otherwise.
Error shapes are not uniform
Three different bodies come back, and the key differs in each. Handle all three rather than reading one field.
| When | Failure body |
|---|---|
| Authentication fails (401) | { "error": "..." } — singular, a string |
| PUT /orders, PUT /skus | { "errors": ["..."] } — plural, an array |
| Everything else | { "message": "..." } |
Status codes: 401 unauthenticated, 403 wrong organization type, 404 order not found, 422 validation or policy refusal.
Fulfillment centers
The fc field, in requests and responses, uses these codes.
| Code | Location |
|---|---|
| dunmore | Scranton, PA |
| dallas | Dallas, TX |
| watsonville | Watsonville, CA |
Sending fc on an order pins it to that center. Leave it out and Crowd Cow picks the center, weighing stock coverage, transit time and cost.
Staging
staging.crowdcow.com mirrors production and is reset from live data nightly. It sits behind HTTP basic auth in addition to the X-Api-Key header — your Crowd Cow contact has the current username and password.
Endpoints
SKUs
PUT /api/fulfillment/v1/skus — create or update a SKU
Keyed on sku_id: a send creates the SKU if it does not exist and overwrites it if it does.
| Field | Type | Notes |
|---|---|---|
| sku_id | string | Required. Your identifier. Stable — this is what order items reference. |
| name | string | |
| weight | number | Pounds. |
| barcode | string | |
| length / width / height | number | Inches. |
curl --request PUT 'https://www.crowdcow.com/api/fulfillment/v1/skus' \
--header 'X-Api-Key: <YOUR_TOKEN>' \
--header 'Content-Type: application/json' \
--data '{
"sku_id": "s_ribeye_12oz",
"name": "Ribeye 12oz",
"weight": 1.5,
"barcode": "0123456789012",
"length": 1, "width": 2, "height": 3
}'
Every field except sku_id is overwritten by what you send, so send the whole record each time — an omitted barcode clears the stored one.
GET /api/fulfillment/v1/skus — list SKUs with inventory
Takes no parameters and returns every SKU on your account in one response, including archived ones (nothing in the payload distinguishes them). There is no pagination on this endpoint.
[
{
"id": "s_ribeye_12oz",
"name": "Ribeye 12oz",
"weight": 1.5,
"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 }
}
}
]
on_hand is physical stock; reserved is committed to orders not yet shipped; available is what you can still sell. Quote against available.
Endpoints
Orders
PUT /api/fulfillment/v1/orders — create or update an order
Keyed on order_id. One order per request; there is no batch endpoint. An order can be updated only while it is still changeable — once it is packed or shipped the request is refused with 422 and a reason. can_modify on any order response tells you in advance.
| Field | Notes |
|---|---|
| order_id | Required. Your internal identifier; the key for every other endpoint. |
| customer_order_id | The number your customer sees. Shown in the portal. |
| items | Required. Array of sku + quantity. sku may be a SKU id or one of your pack codes, which is expanded into its components. |
| shipping_address | Required. name, address_1, address_2, city, state, postal_code, phone_number, delivery_instructions. |
| billing_address | address_1, address_2, city, state, postal_code, phone_number. |
| fc | Pin to a fulfillment center. Omit to let Crowd Cow route. |
| shipping_method | carrier + service. Three meanings — see below. |
| packaging | dry_ice_in_pounds. |
| sender_name | Gift sender. |
| gift_message | Printed on a gift note. Newlines are honored. |
| recipient_name | Gift recipient. |
| recipient_email | Gift recipient. |
| Buyer’s email; used as the gift sender address when one is not set. | |
| preferred_arrival_date | YYYY-MM-DD. The date your checkout promised. See below. |
| external_references | Array of system + key + value. Your own cross-reference ids; searchable in the portal. |
| source_system | Integration attribution. Defaults to the 3PL API. |
| origin_channel | Channel attribution. |
shipping_method has three meanings, and the difference matters.
Omitted — no signal; any existing override is left alone. carrier + service set — pin the order to that service. Both explicitly null — clear a previous override and return the order to automatic routing.
preferred_arrival_date picks the slot, not the service.
Crowd Cow chooses the shipment plan whose arrival is closest to your date, but will not buy a faster, more expensive service to hit it. A date in the past is ignored — that happens more often than you would expect when a storefront replays an older order’s saved date, and honoring it would silently buy air nobody asked for.
The ship-to state must agree with the ship-to ZIP.
A mismatch fails the whole request with 422 and no order is created; resend once corrected. Unknown and non-US ZIPs pass. The city is deliberately not checked.
curl --request PUT 'https://www.crowdcow.com/api/fulfillment/v1/orders' \
--header 'X-Api-Key: <YOUR_TOKEN>' \
--header 'Content-Type: application/json' \
--data '{
"order_id": "1234567",
"customer_order_id": "456",
"shipping_method": { "carrier": "UPS", "service": "Ground" },
"packaging": { "dry_ice_in_pounds": 10 },
"items": [{ "sku": "s_ribeye_12oz", "quantity": 2 }],
"shipping_address": {
"name": "John Doe",
"address_1": "123 Main St", "address_2": "Apt 1",
"city": "Kirkland", "state": "WA", "postal_code": "98033",
"phone_number": "1234567890",
"delivery_instructions": "Gate code 4432"
},
"billing_address": {
"address_1": "123 Billing St", "city": "Seattle",
"state": "WA", "postal_code": "98104"
}
}'
The order object
Returned by every order endpoint.
{
"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": 5 },
"items": [
{ "sku": "s_ribeye_12oz", "quantity": 2, "weight": 1.5 }
],
"shipping_address": {
"name": "John Doe",
"address_1": "123 Main St", "address_2": "Apt 1",
"city": "Kirkland", "state": "WA", "postal_code": "98033",
"phone_number": "(123) 456-7890",
"delivery_instructions": "Gate code 4432"
},
"sender_name": "John Smith",
"gift_message": "Happy birthday!",
"billing_address": {
"address_1": "123 Billing St", "address_2": null,
"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:12:00Z",
"shipped_at": "2026-06-02T01:31:00Z"
}
]
}
Two things worth knowing about this payload. items excludes Crowd Cow packing inserts — you see your own products only, and quantity is what was originally ordered, not what was picked. shipments lists shipped shipments only: an order that has not left the building has an empty array, not a placeholder. Tracking scans, delivery exceptions and a promised-versus-predicted comparison are not available through this API — those live in the portal’s Shipments tab.
GET /api/fulfillment/v1/orders — fetch orders
curl 'https://www.crowdcow.com/api/fulfillment/v1/orders?id=1234567' \ --header 'X-Api-Key: <YOUR_TOKEN>' curl 'https://www.crowdcow.com/api/fulfillment/v1/orders?page=1' \ --header 'X-Api-Key: <YOUR_TOKEN>'
Returns a JSON array in both cases — a single-order lookup is an array of one, or an empty array if nothing matches. There is no 404 on this endpoint.
Limits to design around
id is the only filter — there is no date range, status or text search. Page size is 30 and cannot be changed. And paging is not stably ordered: the result set has no sort, so walking page 1, 2, 3 can repeat and skip rows. Fetch by id where you can, and treat a full-book walk as approximate until we fix this.
POST /api/fulfillment/v1/orders/<order_id>/cancel
Returns the cancelled order. Cancelling an already-cancelled order is a no-op that returns 200, so it is safe to retry. An order too far along returns 422 naming the reason.
PATCH /api/fulfillment/v1/orders/<order_id>/override_ice
Send dry_ice_in_pounds or dry_ice_in_kilograms (converted and rounded). Sending the key with an empty value clears the override and restores the default; omitting both keys is an error, not a clear.
PATCH /api/fulfillment/v1/orders/<order_id>/override_shipping_method
Send both carrier and service, or both empty to clear the override and return to automatic routing. One without the other is a 422. The order is rescheduled onto a plan matching the new service; if none exists for its current ship date the request fails with 422 and nothing changes.
Rescheduling
GET /orders/<order_id>/rescheduling_options lists the dates available, then POST /orders/<order_id>/reschedule moves the order to one of them. Pass an arrival_date from the options plus either the shipping_option_name or a carrier + service pair.
{
"order_id": "1234567",
"current_arrival_date": "2026-06-04",
"current_shipping_option_name": "MEM LH UPS",
"options": [
{
"arrival_date": "2026-06-06",
"fulfillment_date": "2026-06-04",
"transit_days": 2,
"carrier": "UPS",
"service": "Ground",
"shipping_option_name": "MEM LH UPS",
"fc": "dunmore",
"preferred": true
}
]
}
An order that cannot be rescheduled returns 422 with the reason rather than an empty list. No shipping price is returned — 3PL rates are contracted at the account level, so a per-plan fee would be meaningless.
Endpoints
Quoting and dates
Both endpoints are 3PL-only and return 403 otherwise. Neither has a portal equivalent — they exist for your own checkout.
POST /api/fulfillment/v1/quote
An inventory-aware shipping quote for a cart. For each fulfillment center that can ship part of the cart it returns carrier, service, dates and transit time, plus anything unfulfillable and whether the cart still qualifies for ground.
sku_id is your own SKU identifier. quantity must be a positive integer. requested_delivery_date is optional; if it cannot be met, the next available date is quoted and a reason is attached to that shipment.
curl --request POST 'https://www.crowdcow.com/api/fulfillment/v1/quote' \
--header 'X-Api-Key: <YOUR_TOKEN>' \
--header 'Content-Type: application/json' \
--data '{
"destination": { "postal_code": "98101" },
"items": [
{ "sku_id": "s_ribeye_12oz", "quantity": 2 },
{ "sku_id": "s_bison_burger_8pk", "quantity": 1 }
],
"requested_delivery_date": "2026-06-05"
}'
{
"shipments": [
{
"fc": { "code": "dunmore", "display": "ships from your area" },
"items": [
{ "sku_id": "s_ribeye_12oz", "quantity": 2 }
],
"fulfillment_date": "2026-06-02",
"delivery_date": "2026-06-04",
"transit_days": 2,
"rate": {
"carrier": "UPS",
"service": "Ground",
"shipping_option_name": "MEM LH UPS",
"preferred": true
}
}
],
"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."
}
- shipments — one entry per fulfillment center used.
fc.codeis the internal code;fc.displayis a generic customer-safe string that never names the warehouse. Areasonappears only when noteworthy: the requested date was unavailable, or this is a secondary shipment because the primary center was short. - unfulfillable — present only when something cannot be quoted. Each entry carries sku_id, quantity_requested, quantity_available_across_all_fcs and a reason (no_fc_has_inventory, insufficient_inventory_across_all_fcs, no_shipping_options_available, no_eligible_shipment_plan).
- ground_eligibility — eligible is true only when every item ships and every shipment is a ground service. blockers lists what the nearest ground center is short on, each with a recommended_max_quantity — reduce to that to keep ground. eligible false with empty blockers means air was quoted for speed, not for lack of stock.
An empty or fully out-of-stock cart still returns 200 with an empty shipments array. Read shipments, unfulfillable and advisory — not the status code. Validation failures (missing or bad postal_code, non-array items, unknown sku_id, non-positive quantity, malformed date) return 422 with a message. This is an estimate at quote time; Crowd Cow re-routes at order import if stock has moved.
GET /api/fulfillment/v1/shipment_calendar
Available fulfillment and arrival dates to a destination, independent of any cart — for rendering a delivery-date picker. postal_code is required. weeks is optional, defaults to 4, and is clamped to 1–12 (a larger value is clamped, not rejected; a non-integer is a 422).
curl 'https://www.crowdcow.com/api/fulfillment/v1/shipment_calendar?postal_code=98101&weeks=4' \ --header 'X-Api-Key: <YOUR_TOKEN>'
Returns plans, the full list of available shipping dates in the same shape as the reschedule options above, and default_plan — the first preferred plan, or null if none qualifies.
What the API cannot do
Listed deliberately, so you do not build against something that is not there. Each of these is available in the portal today.
- Shipments — tracking status, scan history, late and exception flags, promised versus predicted arrival, lost or destroyed write-offs.
- Invoices — list, detail, PDF, cost workbook.
- Inventory transactions — receiving, picks, cycle counts, write-offs.
- Delivery performance — on-time rate, average transit, alert counts.
- Reports — month-end inventory, Sad Cow write-offs, lots.
- Order search by anything except order_id.
- SKU archiving, internal notes, images, weeks-of-stock and cost figures.
- Packs — you can use a pack code in an order’s items, but creating and editing packs is portal-only.
- Users — account management is portal-only by design.
Rate limits
There are none today. Please keep polling to a sane interval; limits will be introduced before that changes, and will be announced here first.
Something missing?
If one of the gaps above is blocking an integration you are building, tell us — the order we close them in is driven by what partners ask for.
Or email us directly
fulfillment@crowdcow.com