Fulfil Docs

Customer Shipments

Customer shipments are outbound orders assigned to the 3PL's warehouse. The integration polls for new shipments, acknowledges them, updates picking status, and marks them shipped with tracking. All endpoints scope to the warehouse in your JWT — see Authentication.

List customer shipments 🔗

Text
1
GET /v1/customer-shipments.json

Returns the shipments assigned to your warehouse, filtered by lifecycle status. Use this as the polling endpoint to discover work.

Query parameters

Name Type Description
status string pending (default), open, done, canceled
page integer 1-indexed page number (100 records per page)
ids string Comma-separated internal ids to filter by
numbers string Comma-separated human-readable shipment numbers
planned_date_min date Lower bound (inclusive) on planned_date
planned_date_max date Upper bound (inclusive) on planned_date
updated_at_min datetime Lower bound on last modification time
updated_at_max datetime Upper bound on last modification time
carrier_service string Filter by carrier service code

Status meanings

  • pending — assigned to the warehouse, not yet acknowledged by the 3PL
  • open — acknowledged, still being processed
  • done — shipped
  • canceled — cancelled before shipment

Response

JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
  "data": [
    {
      "id": 100123,
      "number": "CS-100123",
      "company_code": "ACME",
      "warehouse": { "id": 3, "code": "LAX", "name": "Los Angeles" },
      "priority": "3",
      "planned_date": "2026-06-20",
      "customer": {
        "name": "Jane Doe",
        "email": "jane@example.com",
        "code": "CUST-42"
      },
      "delivery_address": {
        "address1": "123 Main St",
        "city": "Los Angeles",
        "zip": "90012",
        "country_code": "US",
        "subdivision_code": "CA"
      },
      "carrier_code": "ups",
      "service_code": "ground",
      "lines": [
        {
          "id": 5001,
          "product": { "code": "SKU-100", "name": "Widget" },
          "quantity": 2,
          "uom": "u"
        }
      ]
    }
  ],
  "has_more": false
}

Count customer shipments 🔗

Text
1
GET /v1/customer-shipments/count.json

Same filters as the list endpoint. Returns {"count": <n>}.

Acknowledge customer shipments 🔗

Text
1
PUT /v1/customer-shipments/acknowledge.json

Signals to Fulfil that the 3PL has received the request and committed to fulfil it. The shipment must be in the assigned state and not already acknowledged.

Request body

JSON
1
2
3
4
5
6
{
  "shipments": [
    { "id": 100123 },
    { "id": 100124 }
  ]
}

Response

JSON
1
2
3
4
5
6
{
  "shipments": [
    { "id": 100123, "acknowledged_at": "2026-06-17T18:33:00+00:00", "errors": null },
    { "id": 100124, "acknowledged_at": null, "errors": ["The shipment is cancelled"] }
  ]
}

Returns 200 if every item succeeded, 207 if some failed.

Update picking status 🔗

Text
1
2
PATCH /v1/customer-shipments/change-status.json
POST  /v1/customer-shipments/change-status.json

Update the picking status of one or more shipments. POST is a convenience alias for callers that cannot send PATCH.

Request body

JSON
1
2
3
4
5
6
{
  "shipments": [
    { "id": 100123, "picking_status": "in-progress" },
    { "id": 100124, "picking_status": "done" }
  ]
}

Allowed transitions

  • in-progress — picker has started
  • done — picking complete
  • exception — picking blocked

Shipments already shipped or cancelled cannot transition. Returns 200 / 207 with per-item errors.

Mark customer shipments as shipped 🔗

Text
1
PUT /v1/customer-shipments/ship.json

Mark shipments as shipped, attaching carrier, service, tracking, and per-line quantities. Unlike acknowledge, this endpoint fails fast: if any item has a problem, the entire request returns 400 and no shipments are marked shipped.

The payload accepts either a flat lines list (converted internally into a default single package) or a full packages[] structure supporting per-package tracking, medium/material, and nested inner packages.

Request body

JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
  "shipments": [
    {
      "id": 100123,
      "carrier": "ups",
      "carrier_service": "ground",
      "ship_strategy": "ship_as_available",
      "cost": 12.50,
      "cost_currency_code": "USD",
      "packages": [
        {
          "tracking_number": "1Z999AA10123456784",
          "tracking_url": "https://www.ups.com/track?tracknum=1Z999AA10123456784",
          "carrier_identifier": "ups",
          "medium": "box",
          "material": "cardboard",
          "reference": "WH-PKG-1",
          "override_sscc": "00614141234567890",
          "items": [
            {
              "product_code": "SKU-100",
              "quantity": 2,
              "lot": { "number": "LOT-A" },
              "serial_numbers": ["SN-001", "SN-002"]
            }
          ]
        }
      ]
    }
  ]
}

Root-level fields

Field Type Notes
id integer Required — customer shipment id
carrier string Carrier code (also accepted as carrier_code)
carrier_service string Carrier service code (also accepted as carrier_service_code)
carrier_identifier string Optional carrier identifier
tracking_number string Shipment-level tracking number (or omit and set per-package)
cost number Optional shipping cost
cost_currency_code string Required when cost uses a currency other than the company default
ship_strategy string defer_unshipped, ship_as_available (default), or ship_all_together
lines array Flat item list — mutually exclusive with packages
packages array Full package structure — mutually exclusive with lines

Passing both lines and packages returns 400.

ship_strategy values

  • ship_as_available (default) — available items are shipped immediately in their own split shipment with a unique tracking number; unshipped items remain in the original awaiting more advices
  • defer_unshipped — ship what's available now; move the rest into a new deferred shipment
  • ship_all_together — hold everything until all items are ready; tracking numbers accumulate on the same shipment, no splits

Package fields

Field Type Notes
tracking_number string Package-level tracking
tracking_url string Optional URL for the tracking page
carrier_identifier string Optional carrier identifier for the package
medium string Package medium (see supported values in Fulfil)
material string Package material
reference string Optional external reference
override_sscc string Override the auto-generated SSCC label
items array Line items in the package (required)
consumables array Non-inventory consumables used
children array Nested inner packages — makes this a parent/pallet package. Mutually exclusive with items at this level.

Item fields

At least one of product_code, product_id, or sale_line_id is required. quantity is always required and must be ≥ 1.

Field Type Notes
product_code string SKU
product_id integer Internal product id
sale_line_id integer Internal sale line id (for cross-dock, options)
quantity number Required, ≥ 1
lot.number string Lot number for lot-tracked SKUs
serial_numbers array of string Serial numbers for serial-tracked SKUs
sale_line_cross_dock_location_code string Overrides the shipment's cross-dock location for this line

Consumable fields (per-package, non-inventory items like dunnage, labels)

Field Type Notes
product_code string Required
quantity number Required, ≥ 1

Response

JSON
1
2
3
4
5
{
  "shipments": [
    { "id": 100123, "shipped_at": "2026-06-17T18:33:00+00:00" }
  ]
}

Deprecated fields

  • split (boolean) — legacy alternative to ship_strategy. false maps to ship_all_together, true maps to ship_as_available. Logs a deprecation warning. Use ship_strategy in new integrations.

Report a failure 🔗

Text
1
2
PATCH /v1/customer-shipments/failed.json
POST  /v1/customer-shipments/failed.json

Tell Fulfil that the 3PL could not process specific customer shipments (for example: integration error, missing inventory). Pass an error string per item to surface the reason in Fulfil's UI.

Request body

JSON
1
2
3
4
5
{
  "shipments": [
    { "id": 100123, "error": "Out of stock at picking station" }
  ]
}

Response

JSON
1
2
3
4
5
{
  "shipments": [
    { "id": 100123, "tpl_status": "sending_error", "errors": null }
  ]
}

Returns 200 / 207 with per-item errors.

Get SSCC labels 🔗

Text
1
GET /v1/customer-shipments/{shipment_id}/sscc.json

Generate SSCC (Serial Shipping Container Code) labels for the shipment and each of its packages. Returns signed URLs to PDF labels.

Response

JSON
1
2
3
4
5
6
7
{
  "id": 100123,
  "label_url": "https://.../sscc-shipment-100123.pdf",
  "packages": [
    { "id": 88, "label_url": "https://.../sscc-package-88.pdf" }
  ]
}