Skip to content

Fulfillment partner integration

This guide is for fulfillment partners integrating with ShipperOne — any party that physically fulfils orders on behalf of a tenant. The role is the same regardless of business model:

  • Third-party logistics providers (3PLs) — receive orders, fulfil from contract warehouses, dispatch with their carriers.
  • Dropshippers — receive orders, fulfil from supplier stock, dispatch directly to the customer.
  • In-house warehouses — operated internally by the tenant, often running a WMS; integrate the same way as an external 3PL.

Whichever you are, the API contract is the same: poll the fulfillment order queue, decide whether to fulfil, dispatch the goods, and report back. Allocation and invoicing live in the ERP integration guide; a single integrator may combine both roles.

You should follow this guide if your system needs to:

  • Receive incoming fulfillment orders routed to one of your locations and decide per item whether to fulfil (accept with per-item qty; qty=0 rejects that item).
  • Dispatch the goods with a carrier and report tracking back to ShipperOne (ship).
  • Optionally monitor a read-only failed queue for orders that fell out of the active flow.

Stock allocation and invoicing are handled by the ERP — see the ERP integration guide. A single integrator may combine both roles by following both guides.

For the full stage-by-stage map of how a fulfillment order moves through the system, see Fulfillment order lifecycle in the Integrations overview. The endpoints in scope for this guide:

  • GET /V1/fulfillmentOrders/pending_accept — see API ref
  • GET /V1/fulfillmentOrders/pending_shipment — see API ref
  • GET /V1/fulfillmentOrders/failed — see API ref
  • POST /V1/fulfillmentOrder/accept — see API ref
  • POST /V1/fulfillmentOrder/ship — see API ref

Every action endpoint (accept, ship) takes an explicit items[] list that defines exactly which line items the call operates on. This single payload shape supports both full and partial operations:

  • Full operation — pass every item_id on the fulfillment order. The whole order transitions (accepted / shipped / etc).
  • Partial operation — pass only the subset being acted on (or, on accept, accept some items and reject others in the same call via qty). Listed items transition; remaining items stay in their current state and the fulfillment order takes a partially_* status.

All listed item_id values must belong to the referenced fulfillment order; unknown IDs return 400.

On POST /V1/fulfillmentOrder/accept, every item entry must include a qty field:

  • qty > 0 — accept that quantity of the item. Must equal the ordered qty unless partial-quantity acceptance is enabled for the order.
  • qty = 0 — reject the item. ShipperOne may reroute rejected items to another location.

Accepted and rejected items can be mixed in a single accept call — the items[] array can contain entries with qty: N and entries with qty: 0 together. Items omitted from items[] remain in pending_accept (when partials are enabled) or cause a 400 (when they are not).

Whether a fulfillment order accepts partial accept / ship is governed by ShipperOne configuration. Two levels of control are supported:

  • System-wide — a tenant-level setting enables or disables partial operations across all fulfillment orders.
  • Per fulfillment order — when the sales channel captures explicit customer consent at checkout (e.g. “it’s OK to ship what’s available”), that consent flows into the fulfillment order and enables partials for that order only. Operator override in the ShipperOne admin is also possible for ad-hoc cases.

When partials are not enabled for a given fulfillment order, items[] must contain all of the order’s items; a subset is rejected with 400.

Integration guidance: design for full operation as the default path, and treat partial as an additive case. The payload shape is the same either way.

1. Accept (with per-item qty; qty=0 rejects)

Section titled “1. Accept (with per-item qty; qty=0 rejects)”

Poll for orders awaiting a fulfillment decision:

GET /rest/{store_code}/V1/fulfillmentOrders/pending_accept?page=1
Authorization: Bearer <your-token>
Accept: application/json

For each order, call accept with a per-item qty. qty > 0 accepts the item at that quantity; qty = 0 rejects it. Accept and reject can be mixed across items in a single call:

POST /rest/{store_code}/V1/fulfillmentOrder/accept
Authorization: Bearer <your-token>
Content-Type: application/json
{
"order": {
"order_id": "FO-000012345",
"items": [
{ "item_id": 55501, "qty": 2 },
{ "item_id": 55502, "qty": 0 }
]
}
}

In the example above, item 55501 is accepted (qty 2) and item 55502 is rejected.

Idempotency — re-sending the same decision for items already in the target state is a no-op; the call returns the current order state without error.

After items are accepted, the order moves into the allocation stage — handled by the ERP. Once allocation is confirmed by the ERP, the order appears in your pending_shipment queue. Rejected items may be rerouted by ShipperOne to another location.

Poll for orders ready to dispatch:

GET /rest/{store_code}/V1/fulfillmentOrders/pending_shipment?page=1
Authorization: Bearer <your-token>
Accept: application/json

Pick, pack, and dispatch with your carrier of choice, then report the shipment back to ShipperOne:

POST /rest/{store_code}/V1/fulfillmentOrder/ship
Authorization: Bearer <your-token>
Content-Type: application/json
{
"shipment": {
"order_id": "FO-000012345",
"items": [
{ "item_id": 55501, "qty": 1 },
{ "item_id": 55502, "qty": 1 }
],
"tracks": [
{
"carrier_code": "dhl",
"title": "DHL Express",
"track_number": "JD0123456789"
}
]
}
}

Partial ship is supported on the same payload — pass only the items being dispatched in this shipment, leave the rest for a follow-up ship call. See POST /V1/fulfillmentOrder/ship for the full schema (qty per item, multiple tracks, optional COD amount, package value).

After shipment, the fulfillment order moves into pending_invoice. Invoicing is not part of the fulfillment partner’s scope — it is handled by the ERP, which polls pending_invoice and calls invoice.

Orders that could not complete the active flow — typically those that failed during allocation, shipment or invoicing and were moved out of the active queues — are surfaced via a separate read-only endpoint:

GET /rest/{store_code}/V1/fulfillmentOrders/failed?page=1
Authorization: Bearer <your-token>
Accept: application/json

This queue is diagnostic only. There is no companion action endpoint to resolve a failed order via the API; resolution happens inside ShipperOne (operator intervention or downstream cleanup), after which the order either re-enters an active queue or stays in failed permanently.

Typical uses:

  • Surface stuck orders in your operator dashboard to chase up internally.
  • Reconcile counts (pending_accept + pending_shipment + failed + already-completed = all orders routed to your location).

The response payload shape is the same as the other queues; price/total fields are omitted, mirroring the pending_accept response.

Pagination, polling cadence, and idempotency

Section titled “Pagination, polling cadence, and idempotency”
  • Pagination — every polling endpoint accepts a page query parameter (?page=1); page size is fixed at 100. Iterate until you receive an empty array.
  • Date filter — pass date=yyyy-mm-dd hh:mm:ss to limit results to orders updated since that timestamp. Useful for incremental polling instead of scanning the full queue every time.
  • Polling cadence — recommended: poll each queue every 30–60 seconds during business hours. The failed queue can be polled less frequently (e.g. every few minutes) since it is diagnostic. ShipperOne does not push webhooks today.
  • Idempotencyaccept and ship are safe to retry. If the requested state transition has already been applied, the call returns the current state without re-processing.

All endpoints return JSON-shaped errors:

HTTPCause
400Invalid query/body shape — bad page number, malformed date, unknown item_id, items[] required for non-partial-enabled orders, etc.
401Missing or invalid Bearer token.
403Token lacks the per-action ACL resource (e.g. Shipper_FulfillmentOrder::api_pending_accept). Contact support to extend the token’s scope.
500Unexpected server-side error — safe to retry with exponential backoff.

See each endpoint’s API Reference page for the full per-error trigger list.