Update Sales Order
PUT/api/sales-orders/:salesOrder
Updates a sales order. Accepts a partial payload — only the provided fields are applied.
orders:writeGrant this scope to your token under Settings → Developer → Personal Access Tokens.
Authentication: Requires Bearer token.
Common use cases:
- Reopen a closed order: send
{"order_status": "open", "force_reopen": true} - Close an open order: send
{"order_status": "closed"} - Cancel an order: send
{"canceled_at": "2026-05-08T12:00:00Z"} - Update lines, addresses, or other fields by including only the keys you want to change.
Reopen behavior (force_reopen):
- A closed order is normally not reopened by
order_status: openif itsfulfillment_statusisfulfilledorover_fulfilled. This guard suppresses spurious reopens from sales-channel sync paths (e.g. Walmart Seller-Fulfilled "Shipped") that would leave the order stuck inopenafterupdateFulfillmentStatusruns. - Manual UI reopens express explicit user intent and pass
force_reopen: trueto bypass the guard.
Selected request body fields (all optional unless noted):
- order_status (string): One of draft, open, reserved, closed, cancelled, refunded, completed.
- force_reopen (bool, default false): When true, allow reopening a closed order even if it is fulfilled / over-fulfilled. Only meaningful when
order_status: openis sent on a currentlyclosedorder. - fulfillment_status (string): unfulfilled, partially_fulfilled, fulfilled, over_fulfilled, out_of_sync.
- payment_status (string|null): unpaid, partially_paid, paid, refunded.
- canceled_at (date-time|null): Cancel the order by setting; uncancel by setting to null.
- sales_order_number (string): Must be unique among the orders on the same sales channel. Renaming an order onto a number another order already uses on that channel returns 422; the same number may legitimately exist on a different sales channel. Re-sending an order's own current number is always accepted. When sales_channel_id is sent in the same request, uniqueness is checked against that channel.
- customer_po_number (string|null), payment_date (date|null), fulfilled_at (date-time|null), tracking_number (string|null).
- order_date, ship_by_date, hold_until_date, deliver_by_date — calendar-day fields. Send either a plain
YYYY-MM-DDdate or a full timestamp. A plain date means that calendar day in your account timezone: from an account on America/New_York,2026-09-23is stored as2026-09-23T04:00:00Zand reads back as 23 September in every report and export. Send a full timestamp only when you mean a specific instant — it is stored exactly as given.hold_until_dateis the "do not ship before" release date used for preorders and holds. - payment_term_id (int|null): Payment term for the order; must reference a sales-applicable, non-archived payment term. When null, the customer's default payment term (or the account-wide sales default) applies.
- payment_due_date (date|null): Manual payment due-date override (YYYY-MM-DD). When omitted, the due date is derived from the effective payment term and the order date.
- store_id (int|null), sales_channel_id (int), shipping_method_id (int|null), customer_id (int|null), sales_rep_id (int|null), sales_rep_name (string|null), shipping_address_id (int|null), billing_address_id (int|null), currency_id (int), currency_code (string|null), tax_rate_id (int|null).
- sub_sales_channel_id (int|null): Moves the order into a sub-sales channel, so its COGS, revenue and returns post to that sub-sales channel's nominal code overrides — for example, re-classifying a channel order as a reshipment, a warranty replacement or an affiliate seeding order. The sub-sales channel must belong to the order's sales channel (or to the sales_channel_id sent in the same request); otherwise the request returns 422. Changing it after the order has shipped re-posts the order's existing fulfillment, credit and return entries to the new accounts. Later updates from the sales channel keep the sub-sales channel you set. Send null to clear it. List sub-sales channels with GET /api/sub-sales-channels.
- requested_shipping_method (string|null), packing_slip_printed_at (date-time|null), last_synced_from_sales_channel_at (date-time|null), is_tax_included (bool|null).
- tags (array|null), discount_lines (array|null), sales_order_lines (array of SalesOrderLineData), financial_lines (array of FinancialLineData), custom_field_values (array), notes (array of NoteData), customer (CustomerData|null), shipping_address (AddressData|null), billing_address (AddressData|null).
- Quote fields: is_quote (bool), memo_for_customer (string|null), quote_sent_at (date-time|null), quote_approved_by_customer_at (date-time|null), quote_expires_at (date-time|null).
- Behavioral flags: shouldDeleteFulfillments (bool, default false), shippingAddressChanged (bool, default false), on_hold (bool, default false), deleteFulfillmentsReason (string).
Tax on lines:
- The order's
tax_rate_idis the default rate for lines added later. Changing it never re-rates the order's existing lines, whether or not the same request sendssales_order_lines. To change an existing line's tax, send that line with its owntax_rate_id. - A line sent with
tax_rate_id: null(and notax_allocationof its own) has its tax rate removed and its tax cleared to 0. A line sent without a rate but with its owntax_allocationkeeps that amount. - A line's tax is computed on its discounted amount, not its gross: (quantity × unit price − the line's pre-tax discount) × rate, rounded to 2 decimals. On a tax-inclusive order (
is_tax_included: true) the tax is the part of that discounted amount that is tax.
Picking a saved address by id: send shipping_address: {"id": 812} (or billing_address) with no other address fields to attach one of the customer's saved addresses as-is. The id must be one of the address-book addresses of the customer the order will have after this update (the customer_id in the same request, or the order's current customer). It is refused with 422 on shipping_address.id / billing_address.id when the address does not exist, when it belongs to someone else, or when the order has no customer. An address object that carries address fields (with or without an id) is saved by value as before.
Path parameters:
- salesOrder (required): Sales order ID.
Requires permission: sales_orders.update
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 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.