Set Fulfillment Cost
PUT/api/v2/sales-order-fulfillments/:salesOrderFulfillment/cost
Set a fulfillment's shipping cost to an absolute value and keep the sales order's cost in step — including on CLOSED orders, where the full fulfillment update is rejected. Use this when a carrier invoice or 3PL bill arrives after the order has shipped.
orders:writeGrant this scope to your token under Settings → Developer → Personal Access Tokens.
Behaviour:
- The fulfillment's cost is set to the value sent (an absolute set, not an increment).
- The sales order carries exactly one Shipping Cost line for this fulfillment: it is created when missing, updated in place when present (keeping whatever cost type it already has, e.g. one written by a shipping-provider integration), and removed when cost is null or 0. Duplicate lines for the same fulfillment are collapsed into one.
- Cost lines entered by hand (not linked to a fulfillment) are never touched.
- Idempotent: repeating the same request writes nothing and returns changed: false. Concurrent requests for the same fulfillment are serialised.
- The order is not reopened; its status is unchanged. Profitability and any connected accounting integration pick up the new cost automatically.
Body fields:
- cost (numeric or null, REQUIRED key, >= 0, < 99999.9999). Up to 4 decimal places, in the order's currency. null or 0 removes the Shipping Cost line.
Refused (422):
- the sales order is cancelled (error code SalesOrderIdIsCancelled)
- the fulfillment is voided — restore it first (error code SalesOrderFulfillmentIsCancelled)
Response: sales_order_fulfillment_id, sales_order_id, cost, cost_line (id, financial_line_type_id, financial_line_type_name, amount, description — or null when there is no cost), changed (false when the request was a no-op).
Authentication: Requires Bearer token with the orders scope and the sales_orders.update permission.
Request
Responses
- 200
- 401
- 403
- 404
- 422
- 429
OK
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 Content
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.