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
dunmoreScranton, PA
dallasDallas, TX
watsonvilleWatsonville, 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.

FieldTypeNotes
sku_idstringRequired. Your identifier. Stable — this is what order items reference.
namestring
weightnumberPounds.
barcodestring
length / width / heightnumberInches.
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.

FieldNotes
order_idRequired. Your internal identifier; the key for every other endpoint.
customer_order_idThe number your customer sees. Shown in the portal.
itemsRequired. Array of sku + quantity. sku may be a SKU id or one of your pack codes, which is expanded into its components.
shipping_addressRequired. name, address_1, address_2, city, state, postal_code, phone_number, delivery_instructions.
billing_addressaddress_1, address_2, city, state, postal_code, phone_number.
fcPin to a fulfillment center. Omit to let Crowd Cow route.
shipping_methodcarrier + service. Three meanings — see below.
packagingdry_ice_in_pounds.
sender_nameGift sender.
gift_messagePrinted on a gift note. Newlines are honored.
recipient_nameGift recipient.
recipient_emailGift recipient.
emailBuyer’s email; used as the gift sender address when one is not set.
preferred_arrival_dateYYYY-MM-DD. The date your checkout promised. See below.
external_referencesArray of system + key + value. Your own cross-reference ids; searchable in the portal.
source_systemIntegration attribution. Defaults to the 3PL API.
origin_channelChannel 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.code is the internal code; fc.display is a generic customer-safe string that never names the warehouse. A reason appears 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