BigCommerce update order API: line items, the discount trap, and when to use an app

The BigCommerce update order API is a single endpoint — PUT /v2/orders/{order_id} — and it can change almost everything on an existing order: line items, addresses, status, fees, and totals. It also hides two traps that catch nearly every integration: the PUT clears every discount and promotion on any line item you touch, and it never moves money, because the captured payment is entirely out of its reach. Here is how the endpoint actually behaves, field by field, and the point where scripting stops making sense.
BigCommerce splits order management across two API versions, and the reference docs describe each field without always saying what happens to the rest of the order when you change one. This guide is that missing operational layer: the three request shapes for line items, the documented discount-clearing behavior, the fields that must travel in pairs, and the payment work the API leaves entirely to you. Everything below comes from the current official documentation — no folklore, no guesswork.
Where order updates live: V2 for the order, V3 for the money
Create, read, update, and archive all live on the V2 Orders API, along with order shipments and shipping addresses. The V3 Orders API does not replace it — it adds the money side: order transactions and refunds. That split matters because a realistic edit is rarely just an edit. "Remove one item and give the money back" spans both versions: a PUT /v2/orders/{order_id} to change the line, then a V3 refund quote and refund to return the difference. Authentication is the same for both: a store-level API account token in the X-Auth-Token header, with the Orders scope set to modify (store_v2_orders). One quirk worth knowing: sending is_deleted: true in a PUT archives the order, exactly like the DELETE request.
Line items: the three request shapes
All line-item surgery happens through the products array of the PUT body, and the shape of each entry decides what happens. First, know that an order product has its own id — distinct from the catalog product_id — so always fetch GET /v2/orders/{order_id}/products before writing anything.
- Add an item: include an entry without an
id. Reference a catalog item withproduct_idandquantity, and includeproduct_optionsif the product has variants — omit them and the API has no way to know which size or color you meant. You can also add a custom line with just anameand prices. - Change an item: include the order product's
idand only the fields you want to change. Despite the PUT verb, sub-resources like products behave like PATCH: omitted fields stay untouched. - Remove an item: include the
idand setquantityto0.
That asymmetry — PATCH-like products inside a PUT request — is the first thing to internalize, because the next two sections cover the places where the API does not behave gently.
The documented trap: PUT clears discounts on every line you touch
Straight from the Update Order reference: "After the update, the PUT request clears all discounts and promotions applied to the changed order line items." The documentation even explains why — order data syncs with external systems like Amazon and eBay, so a changed line is reset to its default state. This is by design, not a bug, and it will not be "fixed."
The practical consequence: a customer ordered three shirts in a 20%-off promotion, support bumps one shirt's quantity through your script, and that line silently reverts to full price. The order total goes up, the customer's bank statement doesn't, and your books no longer reconcile. You cannot simply write the discount back, either — coupon_discount is a read-only field. The workable pattern is: snapshot the order's applied discounts from the GET response before writing, then re-apply the math yourself by overriding the line's price_ex_tax and price_inc_tax to the discounted values, or by overriding the order-level subtotal and total pairs. And decide who absorbs the difference before you automate, not after the first angry email.
Fees and totals: the fields that travel in pairs
Order fees are the opposite of products: they follow strict PUT semantics. Any fee not included in your request body is deleted; a fee with an id is updated; a fee without one is created. So if an order carries fees and your body omits them, you just silently removed them — always echo back every fee you want to keep, id included.
Totals come with a pairing rule. To override the subtotal you must send subtotal_ex_tax and subtotal_inc_tax together; same for total_ex_tax and total_inc_tax. Send one without the other and the request fails validation. The docs add a warning that applies beyond fees: tax-inclusive and tax-exclusive values should be consistent with the relevant tax class, because incorrect figures "may lead to issues in downstream operations like refunds." In other words, sloppy tax math doesn't break today — it breaks three weeks later, inside a refund.
What the API will never do: move money
payment_status is read-only, and there is no "charge the customer" call anywhere in the Orders API. A PUT rewrites the order record; the captured payment stays exactly as it was. Raise the total by adding an item and BigCommerce will not collect the difference; lower it by removing one and nothing flows back to the card. This mirrors how the platform behaves in the control panel — BigCommerce cannot re-charge a captured card, full stop. Your integration owns the reconciliation: collect upward differences with a payment request or a separate order, and return downward differences through the V3 refunds endpoints — the partial refund flow has its own traps worth reading first.
Webhooks: closing the loop without creating one
Every successful update fires the store/order/updated webhook — including updates your own script just made. The payload is deliberately lightweight: a scope, a store hash, and the order id. No diff, no fields, no before-and-after. Your handler is expected to call GET /v2/orders/{order_id} and work out what changed. Two defensive habits pay off here. First, guard against self-triggered loops: if your integration both writes orders and listens to store/order/updated, a careless handler will react to its own writes forever. Diff against your last-known state before acting. Second, treat the webhook as a hint rather than a guarantee and re-fetch on a schedule, because missed deliveries happen on every platform.
A safe-update runbook
- GET before every PUT. Snapshot products (with their order-product ids), fees, discounts, and totals.
- Send the smallest possible body. Every product line you include counts as "changed" — and changed lines lose their discounts. Never echo the whole products array back.
- Echo every fee you want to survive, each with its
id. - Re-apply discount math explicitly via line price overrides or the subtotal/total pairs.
- Verify the response totals against what you expected before touching the payment side.
- Reconcile money deliberately: payment request for increases, V3 refund for decreases.
- Rehearse on a sandbox store with a discounted, multi-line, fee-carrying order — the worst case, not the happy path.
When an API script is the wrong tool
The Orders API is the right layer for system-to-system work: ERP sync, 3PL handoffs, marketplace imports, bulk status moves. It is the wrong layer when the human asking for the change is your customer. Then the script grows into a product: customer authentication, an edit UI, a time window and rules for what may change, discount preservation, payment-delta collection, confirmation emails — plus every trap above, handled correctly, forever. That is exactly the gap apps exist to fill. OrderEdit.io gives BigCommerce customers a self-service window to fix addresses, swap variants, add items, or cancel — inside rules you control, with the discount and payment reconciliation already done — from $39/month with a 21-day free trial. Script the machines; give the humans a button.
Try it free on your store
Install OrderEdit.io from the BigCommerce Marketplace and let customers edit and upsell themselves. 21-day free trial.
Start free trial