Skip to main content

Overview

POST /v1/orders is the atomic create endpoint for all order document types (quotes, sales orders, invoices, purchase orders, and returns). A single request can create the order header plus all inline children:
  • Line items with automatic pricing (qty-break levels, account defaults)
  • Header-level discount (percentage or flat)
  • Automatic tax via AvaTax or local rates
  • Inventory allocation per line item
  • Inline payment charged after commit
The response is the fully hydrated order with line_items, payments, and tax_lines expanded inline. You never need a follow-up GET to see what was created. document_type is the one required field and account_id is optional (send it whenever the order is for a customer); the key needs the orders:write scope. If the account does not belong to your entity, or an address id you send does not, the call returns 404 account_not_found or 404 address_not_found, and nothing is created. An id from another entity and an id that does not exist give the same answer.

Canonical scenario: 3 line items + discount + tax + allocation + payment


Document types


Auto-populate from account defaults

When you provide an account_id, Arcus auto-resolves:
  • Shipping and billing address from the account’s default shipping and default billing addresses
  • Pricing level from the account’s default pricing level
  • Payment terms from the account, then the sales channel, then the entity default
  • Sales channel from the account’s default sales channel
  • Tax exemption from the account’s tax-exempt flag and AvaTax entity code
You only need to provide overrides. A minimal valid request is {document_type, account_id}.

Line items

  • Pricing engine runs automatically: qty-break pricing levels, account default pricing level. Override with sell_rate, or with its alias unit_price. Sending both with different values returns 400 price_field_conflict, and a zero or negative price returns 422 invalid_price. When an override differs from the catalog list price, the order is still created and the response carries a warnings array naming the line, the catalog price and the applied price; the override is also recorded in the order’s activity history.
  • Variant guard: if the product has variants, variant_id is required. Otherwise the request returns 422 validation_error with param: "line_items[N].variant_id".
  • Kit expansion: kit products automatically expand their components as child line items (zero-dollar children). No additional calls needed.
  • Any child failure rolls back all items and the order header. Nothing is persisted on validation error.

Discount

Supported types: The discount is recorded as a discount adjustment on the order. value must be 0 or more, and a percentage cannot exceed 100; either mistake returns 400 validation_failed with param: "discount.value".

Automatic tax (auto_compute_tax)

Arcus works out tax after the lines are inserted, using AvaTax or your local tax rates if AvaTax is not configured. Any request that includes line items gets this calculation. Setting auto_compute_tax: true also runs it again after automatic coupons have been applied, so the final tax reflects them. Results appear in tax_lines[] and tax_total in the response, and the totals (subtotal, tax_total, shipping_total, order_total) are always numbers. Requirements:
  • shipping_address_id must resolve to a taxable US jurisdiction.
  • Tax-exempt accounts (with avatax_entity_code) are auto-detected and tax_total will be 0.
Tax calculation is non-blocking: if AvaTax is unavailable, the order is still created and tax_total defaults to 0. You can trigger a re-calculation later via POST /v1/orders/{id}/recalculate.

Inventory allocation (auto_allocate_inventory)

When true, Arcus reserves stock for each line item. A reservation holds the units for this order; it does not ship them.
  • Location: uses location_id from the request, or entity default location.
  • Shortfall guard: if ANY line item has insufficient available stock, the entire request returns 422 insufficient_inventory with per-product shortfall details. The rollback is atomic — nothing is allocated if any line fails.
  • Release: inventory is automatically released on order cancel or void.

Example shortfall response


Inline payments (payments[])

Each entry needs payment_method_id and a positive amount; a missing one returns 400 validation_failed with the path in param. payment_type defaults to card, and save_card: true asks Arcus to keep the card on the account for later use. Inline payments are meant for a payment method already saved on the account. Take cash, check, credit-memo, terms and external payments with the separate call below. In this inline form, payment_method_id is the Arcus internal UUID of a saved method (the id in the list from GET /v1/accounts/{id}/payment-methods), not the Stripe pm_xxx id. Payment fires after the order is committed to the database. This matches industry behavior: if the payment fails, the order still exists with payment_status: unpaid, and the create call still returns 201. A failed entry shows up in _inline_payment_results[] with its index, an error and a hint; for example payment_method_not_found means the id does not belong to your entity, and payment_failed means the charge did not go through. Retry it with the call below. Inline payment results appear in _inline_payment_results[] alongside the main response.

Taking a payment later (POST /v1/orders/{id}/payments)

Use this to retry a failed inline payment, to collect a balance, or to record a payment that is not a saved card. The key needs both orders:write and payments:write.
Other fields depend on the type: amount_tendered for cash, check_number for a check, credit_memo_id for a credit memo, and external_reference for an external payment. A successful card payment returns 200 with data.amount_charged, data.processing_fee, data.new_payment_status, data.payment_intent_id and data.journal_entry_id. A payment larger than the balance due is not rejected: the excess becomes a credit memo on the account (overpayment_credit and credit_memo_id in the result). Taking a payment posts its journal entry, updates the order’s amount_paid, balance_due and payment_status, and fires the payment events described under Webhook events.

Recalculating totals (POST /v1/orders/{id}/recalculate)

Call this after a change that happened outside the create call, such as a manual line edit, a tax jurisdiction change or a pricing rule update. It takes no body, needs orders:write, and returns the order with its subtotal, tax, shipping, discount and order_total worked out again.

Idempotency

Include the Idempotency-Key header to safely retry without double-creating the order. The same key returns the same response (including the same order number) without re-running any DB writes or Stripe charges.
Use a stable key tied to your create session (e.g. session ID + timestamp). Keys expire after 24 hours.

GL behavior

Creating a quote or a sales order posts no ledger entry. The general ledger is affected later:
  • Fulfillment: COGS debit, inventory credit
  • Payment: each payment posts its own journal entry, and the card payment result returns its journal_entry_id
Quotes and sales orders are subledger-only until they are fulfilled and invoiced. See GL Fundamentals for the full posting map.

Webhook events

Subscribers on order.created receive this event immediately after the order is committed. A payment that goes through fires the events of the payment family (for example payment.created and payment.succeeded). See Webhooks for event schemas and retry policy.

Side effects of creating an order

  • A confirmation email goes to the account’s email address when the request includes line items. If the account has no email on file, nothing is sent and the skipped send is logged.
  • Automatic coupons that apply to the order are evaluated and applied, and the totals are recalculated.
  • Reserved stock (with auto_allocate_inventory) is held until the order ships, is cancelled or is voided.

Error reference