Void a posted order
Authorization: requires orders:write API-key scope AND the per-user
orders.delete permission (SoD parity sweep 2026-05-16; the api-v1
wrapper used to gate on scope alone, which let any junior staff with a
orders:write API key bypass the role check the human-user path enforces).
Void is the heavier action than cancel: it reverses GL on partially
fulfilled orders too, so the per-user permission required is
orders.delete (NOT orders.edit). System-actor calls without an
identifiable principal are denied per AICPA SoC2 CC6.1. Idempotent via
Idempotency-Key.
Voids a sales order or invoice. Mirror of POST /orders/{id}/cancel with a
permissive fulfillment guard: voidOrder is the canonical handler for
reversing a partially-fulfilled or fulfilled order when an RMA is not the
right path (e.g. customer dispute, fraud, data entry error pre-shipment).
Voiding reverses every booked GL JE (FULFILLMENT / TAX / SHIPPING / DISCOUNT),
releases inventory reservations, voids Shippo labels carrier-side, voids the
AvaTax committed transaction, and auto-voids any uncaptured Stripe
PaymentIntents with cancel_reason='order_voided'.
The order must not be already voided, archived, fully fulfilled (use returns
instead), partially fulfilled (operator must void packages first), or have
active shipments. If the order has child drop-ship purchase orders or child
work orders auto-created on confirm via the forward cascade, the same atomic
cascade behavior as POST /orders/{id}/cancel applies:
- If any child drop-ship PO has received goods (purchase_receipts exist),
HTTP 409
dropship_po_received_blocks_so_void. - If any child drop-ship PO was sent to the vendor but has no receipts,
HTTP 409
dropship_po_sent_blocks_so_void. - If any child work order has status
completedorclosed, HTTP 409manufactured_wo_completed_blocks_so_void. - If any child work order has status
in_progress, HTTP 409manufactured_wo_in_progress_blocks_so_void. - If any open RMA exists against the order, HTTP 409
open_rma_blocks_void. - Otherwise (every child is in a cascade-eligible state), all child POs +
WOs cancel atomically inside the same transaction as the SO void.
The response surfaces
cascaded_drop_ship_pos[]andcascaded_work_orders[]listing each child auto-cancelled by the cascade.
Behavior, atomicity, and error codes are guaranteed mirrors of the cancel path (NEW-GAP-CANCEL-SALES-ORDER-ORPHANS-DROPSHIP-PO 2026-05-15 + NEW-GAP-INVERSE-CASCADE-AUDIT-SWEEP Phase A 2026-05-15). For the underlying invariants see the internal correctness rules (ERP-CORRECTNESS-RULES §E3c + §E3d).
Authorizations
API key issued per entity via Settings > Developers > API Keys.
Each key carries scopes (e.g. orders:read, products:write).
Bearer token format: Authorization: Bearer ark_live_ent_Test keys use ark_test_ent_. Both are issued per entity
via Settings > Developers > API Keys.
Headers
Client-generated unique key for idempotent POST/PATCH/DELETE operations. Alias for the Idempotency parameter. Max 255 chars. On retry with the same key, the original response is returned without re-executing the operation. Keys expire after 24 hours.
255Path Parameters
Body
Response
Voided order. Response may include cascaded_drop_ship_pos[] listing
child drop-ship POs auto-cancelled by the cascade and
cascaded_work_orders[] listing child work orders auto-cancelled by
the cascade. Includes voided_payment_intents[] for any uncaptured
Stripe PIs auto-voided post-commit (AUDIT-077b-G4) and void_failures[]
for any post-commit non-blocking failures (Shopify sync, AvaTax void).
An Arcus ERP order document. One table holds quotes, sales_orders, invoices, returns, and purchase_orders -- always filter by document_type. entity_id is always from the API key (Layer 1 isolation).
order quote, sales_order, invoice, return, purchase_order draft, confirmed, partially_fulfilled, fulfilled, cancelled, voided, awaiting_ach_clearance, on_hold unpaid, partially_paid, paid, overpaid, refunded, partially_refunded, voided unfulfilled, partially_fulfilled, fulfilled Derived (display-only): SUM(list_price * qty) over non-kit lines. The gross baseline of the gross-to-net bridge (List price minus pricing_savings equals subtotal). Never part of order_total.
Derived (display-only): SUM(pricing_rule_adjustment * qty) over non-kit lines (positive magnitude). Per-unit pricing-rule savings already baked into subtotal via sell_rate (ASC 606 transaction price); shown as an informational List-to-Subtotal bridge, never subtracted from order_total.
SSOT: utils/ar-helpers.mjs::updateARBalance. Do not write directly.
AR receivable (the amount this document is owed AS A RECEIVABLE). Forced to 0 on DRAFT sales documents, on cancelled / archived / expired orders, and on voided or fully-refunded invoices, because none of those carries a receivable: a draft is not a receivable, and a terminal document owes nothing. This is the number that ties to AR aging, the AR outstanding total, and the GL AR control account. For the amount still COLLECTIBLE on any document at any status (including a draft), use amount_due. Purchase orders and quotes are never forced to 0. SSOT: utils/ar-helpers.mjs::updateARBalance. Do not write directly.
Collectible remainder: order_total minus amount_paid, rounded to 2 decimals. Status-blind BY DESIGN, so it answers "how much is still OUTSTANDING on this document as simple arithmetic" even where balance_due is 0 because the document is not (yet) a receivable. Negative when the customer has overpaid (money owed back to them). On a confirmed, non-refunded document with no sales allowance and no cancellation, amount_due and balance_due are equal. NOT A PAYABLE AMOUNT. Because it is status-blind it stays positive on documents that can accept no payment at all -- a cancelled / archived / expired order, a voided invoice, a fully-refunded order -- and it OVERSTATES what is owed on an allowance-settled order (a standalone concession refund reversed goods or tax: balance_due correctly settles to 0 while the remainder does not). Collecting amount_due on either shape creates an overpayment, and the payment endpoints will refuse it with order_not_payable (422). To decide how much to collect, take the amount from the payment surface you are about to call (POST /v1/orders/{id}/payments validates and caps server-side); to reconcile against AR aging or the GL AR control account, use balance_due.
ship, pick_up, local_delivery order, manual, proforma, correction 
