> ## 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.

# Creating Orders

> Create a sales order with line items, tax, inventory allocation, and inline payment in a single API call.

## 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

<CodeGroup>
  ```bash curl theme={null}
  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
        }
      ]
    }'
  ```

  ```typescript TypeScript theme={null}
  import Arcus from "@arcuserp/sdk";

  const arcus = new Arcus({ apiKey: "ark_live_ent_wss_abc123xyz" });

  const order = await arcus.orders.create({
    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.0,
        payment_type: "card",
        auto_capture: true,
      },
    ],
  });

  console.log(order.data.order_number); // SO-0012345
  console.log(order.data.line_items);   // 3 items hydrated
  console.log(order.data.tax_total);    // number (not string)
  ```

  ```python Python theme={null}
  import arcuserp

  arcus = arcuserp.Arcus(api_key="ark_live_ent_wss_abc123xyz")

  order = arcus.orders.create(
      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,
          }
      ],
  )

  print(order.data.order_number)  # SO-0012345
  print(order.data.tax_total)     # float (not string)
  ```
</CodeGroup>

***

## 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

```json theme={null}
{
  "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

```json theme={null}
{
  "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`)

```json theme={null}
{"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`)

```json theme={null}
{"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

```json theme={null}
{
  "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[]`)

```json theme={null}
{
  "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.

```bash theme={null}
-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](/guides/general-ledger-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](/concepts/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                                                  |
