Fulfill Sales Order
POST/api/sales-orders/:salesOrder/fulfill
Records a fulfillment (shipment) against a sales order, deducting inventory and, by default, submitting the fulfillment to the warehouse's shipping provider and reporting it to the originating sales channel.
orders:writeGrant this scope to your token under Settings → Developer → Personal Access Tokens.
Authentication: Requires Bearer token.
Request body fields:
- fulfillment_type (string, required): How the order was/will be fulfilled. One of: manual, shipstation, starshipit, shipmyorders, fba, walmart_wfs, veracore, shiphero, shipfusion, d3, trackstar, pickup, sales_channel.
- warehouse_id (integer, optional): Warehouse to fulfill from; inventory is deducted from this warehouse. Auto-resolved when every product line can only ship from one warehouse; required when lines span multiple warehouses. A line can ship from the warehouse it is routed to or from any warehouse it has been split to (a line split across warehouses ships each portion from its own warehouse).
- requested_shipping_method_id (integer, optional): Mapped shipping method to request from the provider.
- requested_shipping_method (string, optional, max 255): Free-text requested shipping method.
- fulfilled_shipping_method_id (integer, optional): The shipping method actually used (manual fulfillments).
- manual_fulfilled_shipping_method (string, optional, max 255): Free-text actual shipping method (manual fulfillments).
- fulfilled_at (date-time, required when fulfillment_type is manual): When the shipment left the warehouse.
- cost (number, optional, < 100000): Shipping cost.
- tracking_number (string, optional, max 255): Carrier tracking number.
- fulfillment_sequence (integer, optional): Sequence number; must be unique within the sales order.
- packing_slip_printed_at (date-time, optional): When the packing slip was printed.
- status (string, optional): Initial fulfillment status.
- submit_to_shipping_provider (boolean, optional, default true): Set false to record the fulfillment without notifying the shipping provider.
- submit_to_sales_channel (boolean, optional, default true): Set false to record the fulfillment without reporting it to the sales channel.
- fulfillment_lines (array, optional, min 1 when present): Lines and quantities to fulfill. When omitted, defaults to every unfulfilled product line in the resolved warehouse at its full remaining quantity.
- fulfillment_lines.*.sales_order_line_id (integer, required): Sales order line to fulfill; must belong to the order and be fulfillable from the resolved warehouse (its routed warehouse, or a warehouse it has been split to).
- fulfillment_lines.*.quantity (number, required, >= 0, < 100000): Quantity to fulfill; cannot exceed the line's remaining-to-fulfill quantity (already-shipped and in-flight quantities are subtracted).
- fulfillment_lines.*.metadata.fba_seller_sku (string, optional): Amazon seller SKU override for FBA fulfillments.
- fulfillment_lines.*.lot_allocation (array, optional): Per-line lot override for lot-tracked products. When omitted, lots are consumed first-expired-first-out. Entries must reconcile to the line quantity and reference active lots of the line's product and warehouse.
- fulfillment_lines..lot_allocation..fifo_layer_id (integer, required with lot_allocation): Inventory lot/layer to consume from.
- fulfillment_lines..lot_allocation..quantity (number, required with lot_allocation, > 0): Quantity to take from that lot.
- fulfillment_lines.*.pick_allocations (array, optional): The bins the line's units were picked from. The quantities must sum to the line's quantity.
- fulfillment_lines..pick_allocations..warehouse_location_id (integer, required with pick_allocations): A bin (warehouse location). It must be in the warehouse the line ships from (the request's
warehouse_id, or the line's own warehouse when omitted); a bin in another warehouse returns 422 on this field with codePickBinInAnotherWarehouse. - fulfillment_lines..pick_allocations..quantity (number, required with pick_allocations, > 0): Units picked from that bin.
- fulfillment_lines..pick_allocations..warehouse_location_id (integer, required with pick_allocations): A bin (warehouse location). It must be in the warehouse the line ships from (the request's
- metadata.signature_required (boolean, optional): Require a delivery signature.
- metadata.saturday_delivery (boolean, optional): Request Saturday delivery.
- metadata.shipping_speed (string, optional): Provider shipping speed hint.
- metadata.comments (string, optional): Comments passed to the provider.
- metadata.veracore_packing_slip_comments (string, optional, max 500): Packing slip comments (Veracore fulfillments).
- metadata.veracore_shipping_comments (string, optional, max 500): Shipping comments (Veracore fulfillments).
- metadata.veracore_third_party_account_number (string, optional, max 100): Third-party billing account number (Veracore fulfillments).
- metadata.veracore_third_party_type (integer, optional, one of 1, 2, 3): Third-party billing type (Veracore fulfillments).
Behavior:
- The order must be approved (not draft). Closed orders can only be fulfilled while their fulfillment status is out_of_sync.
- Fulfillments recorded with a Personal Access Token are treated as external/channel imports (the order was shipped outside SKU.io) rather than shipments dispatched from SKU.io.
- Insufficient allocated inventory returns 422 with a structured
shortfallslist, and the shortfall is also recorded as a note on the order. - If the shipping provider times out, the fulfillment is queued and processed in the background; the request still succeeds with a processing message.
Path parameters:
- salesOrder (required): Sales order ID.
Requires permission: sales_orders.fulfill
Mixed delivery: on an order that mixes shipped items and Click & Collect (in-store pickup) items, one fulfillment may only carry lines of one delivery method — a request whose lines (or, when fulfillment_lines is omitted, the remaining lines at the warehouse) span both returns 422. Lines with quantity 0 are ignored for this check.
Serial numbers: for serial-tracked products, a serial that can't be shipped (unknown, already shipped, or not in stock) or that is listed twice returns 422 with a message naming the serial.
Locked accounting period: a fulfilled_at on or before the accounting lock date returns 422 with error code InventoryPeriodLocked under errors.fulfilled_at (its data carries date and lock_date). Nothing is created; resubmit with a date after the lock date, or move the lock date back first.
Shipping provider did not answer: when the order is submitted to a shipping provider and the provider does not answer in time, the request returns 502. SKU.io cannot tell whether the provider created the shipment, so nothing is retried automatically — check the order in the provider before fulfilling again.
Request
Responses
- 200
- 400
- 401
- 403
- 404
- 422
- 429
- 502
OK
Response Headers
Bad Request
Response Headers
Unauthenticated — the bearer token is missing, revoked, expired, or malformed. Never retry automatically; fix the credential. See the Errors guide.
Forbidden — the token lacks a required scope, the endpoint is not available to API tokens, or the user behind the token lacks the permission. A human must adjust the token scopes or user permissions; do not retry.
Not found — no record with the given identifier (or the route does not exist). Verify the ID before retrying.
Unprocessable Entity
Response Headers
Rate limited — platform limit is 1,000 requests/min; individual tokens may carry lower limits. Honor the Retry-After header before retrying. See the Rate Limits guide.
Bad Gateway