Documentation Index
Fetch the complete documentation index at: https://docs.arcuserp.com/llms.txt
Use this file to discover all available pages before exploring further.
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.
Canonical scenario: 3 line items + discount + tax + allocation + payment
curl -s -X POST "https://api.arcuserp.com/v1/orders" \
-H "Authorization: Bearer ark_live_ent_wss_abc123xyz" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"document_type": "sales_order",
"account_id": "c3f2a7b1-0e44-4f89-9d12-aabb0c123456",
"location_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"shipping_address_id": "d4e5f6a7-b8c9-0123-def4-567890abcdef",
"billing_address_id": "e5f6a7b8-c9d0-1234-ef56-7890abcdef01",
"line_items": [
{"product_id": "11111111-2222-3333-4444-555555555555", "quantity": 5},
{"product_id": "22222222-3333-4444-5555-666666666666", "quantity": 3},
{
"product_id": "33333333-4444-5555-6666-777777777777",
"variant_id": "44444444-5555-6666-7777-888888888888",
"quantity": 10
}
],
"discount": {"type": "percentage", "value": 5},
"auto_compute_tax": true,
"auto_allocate_inventory": true,
"payments": [
{
"payment_method_id": "55555555-6666-7777-8888-999999999999",
"amount": 750.00,
"payment_type": "card",
"auto_capture": true
}
]
}'
Document types
document_type | Use for | Account type |
|---|
quote | Non-binding estimate | Customer |
sales_order | Standard order (confirm to allocate) | Customer |
invoice | Billable invoice (auto-posts to AR) | Customer |
return | Return merchandise authorization | Customer |
purchase_order | Purchase from a vendor | Vendor |
Auto-populate from account defaults
When you provide an account_id, Arcus auto-resolves:
- Shipping and billing address from
is_default_shipping / is_default_billing flags
- Pricing level from
accounts.default_pricing_level_id
- Payment terms from account, then channel, then entity-level default
- Sales channel from
accounts.default_sales_channel_id
- Tax exemption from
accounts.tax_exempt and accounts.avatax_entity_code
You only need to provide overrides. A minimal valid request is {document_type, account_id}.
Line items
{
"line_items": [
{"product_id": "...", "quantity": 5},
{"product_id": "...", "variant_id": "...", "quantity": 3},
{"product_id": "...", "quantity": 1, "sell_rate": 49.99, "notes": "Special price"}
]
}
- Pricing engine runs automatically: qty-break pricing levels, account default pricing level.
Override with
sell_rate.
- 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
{
"discount": {"type": "percentage", "value": 5}
}
Supported types:
| Type | Behavior |
|---|
percentage | Applied to line item subtotal sum (before tax). value = 0-100. |
flat | Fixed dollar amount deducted from subtotal. |
The discount is stored as an order_adjustment row with adjustment_type: 'discount'.
Automatic tax (auto_compute_tax)
{"auto_compute_tax": true}
When true, Arcus calls AvaTax (or local tax rates if AvaTax is not configured) after all
items are inserted. Results appear in tax_lines[] and tax_total in the response.
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)
{"auto_allocate_inventory": true}
When true, Arcus reserves stock per line item via ORDER_RESERVATION inventory transactions.
- 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
{
"error": "insufficient_inventory",
"code": "insufficient_inventory",
"type": "validation_error",
"param": "line_items[2].quantity",
"hint": "Insufficient inventory for product 33333333-.... Requested: 10, available: 4.",
"product_id": "33333333-...",
"requested": 10,
"available": 4
}
Inline payments (payments[])
{
"payments": [
{
"payment_method_id": "55555555-...",
"amount": 750.00,
"payment_type": "card",
"auto_capture": true
}
]
}
payment_method_id is the Arcus internal UUID from account_payment_methods (not the Stripe pm_xxx ID).
Use GET /v1/accounts/{id}/payment-methods to list available methods.
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. Retry the payment via
POST /v1/orders/{id}/payments.
Inline payment results appear in _inline_payment_results[] alongside the main response.
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.
-H "Idempotency-Key: order-session-2026-05-12-001"
Use a stable key tied to your create session (e.g. session ID + timestamp). Keys expire after 24 hours.
GL behavior
No GL entries fire at order create. The general ledger is only affected at:
- Fulfillment: COGS debit, inventory credit
- Payment: AR debit, cash/revenue credit
This matches NetSuite and Acumatica behavior. Quotes and sales orders are subledger-only until
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.
If inline payments succeed, payment.received fires as well. See Webhooks
for event schemas and retry policy.
Error reference
| Code | HTTP | Meaning |
|---|
missing_field | 400 | Required top-level field missing (document_type, account_id) |
validation_failed | 400 | Inline child validation failure (see param for exact path) |
line_item_failed | 400 | A specific line item was rejected (inactive product, box type, etc.) |
insufficient_inventory | 422 | auto_allocate_inventory shortfall — see product_id, requested, available |
account_role_mismatch | 422 | Sales order against a vendor account (or PO against a customer account) |
tax_exempt_requires_exemption_code | 422 | tax_exempt: true but no avatax_entity_code on order or account |
payment_method_not_found | 404 | Inline payment_method_id UUID does not exist for this entity |
insufficient_scope | 403 | API key lacks orders:write scope |