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

# Confirm a draft sales order or invoice into the OPEN state

> Transitions a draft sales order or draft invoice into the OPEN state, runs the
approval-threshold + credit-hold + delinquency gates, and triggers inventory allocation
for every line item. The order must have at least one line item and a valid shipping
address. Confirmation generates an order-confirmed activity log entry and optionally
sends a confirmation email when `send_email: true` is passed.

**Quotes cannot be confirmed.** A document with `document_type='quote'` returns HTTP 422
with `code: 'quote_must_convert_before_confirm'`. The canonical quote-to-sales-order
doorway is `POST /orders/{id}/convert`; after conversion, call `/confirm` on the
resulting sales order. This mirrors NetSuite Estimate, Acumatica Sales Quote, Odoo
Quotation, and QuickBooks Estimate behavior (quotes do not commit inventory).

**Credit hold override (NEW-GAP-CS-CREDIT-HOLD-OVERRIDE-WITH-PERMISSION-AND-AUDIT,
2026-05-16).** When an account is on credit hold (manual flag or credit-limit
exceeded), confirmOrder normally returns HTTP 409 with `error: 'Order placed on
hold'` and places the order in `is_on_hold = true`. An operator with the
`accounts.override_credit_hold` permission can push the order through the gate
by passing `override_credit_hold: true` AND `override_reason` (minimum 10 chars
after trim) in the request body. The override is audit-logged via three channels:
(1) `activity_log.action = 'order_credit_hold_overridden'`, (2) `order_timeline`
entry, and (3) `order.credit_hold_overridden` realtime broadcast. The order also
records `orders.credit_hold_override_at / _by / _reason / _type` for reporting.
Industry: NetSuite "Override Credit Hold" right, Acumatica `SO.OverrideHold`,
Brightpearl override-credit-limit prompt.

**Delinquency acknowledgement (NEW-GAP-DELINQUENCY-ACKNOWLEDGE-FE-MODAL-AND-OPENAPI,
2026-05-18).** When an account has overdue invoices (but is not on hard credit hold),
confirmOrder returns HTTP 422 `requires_delinquency_acknowledgment` with
`overdue_count`, `overdue_amount`, and `max_days_overdue`. An operator with the
`accounts.acknowledge_delinquency` permission can push through by retrying with
`acknowledge_delinquency: true` AND `acknowledge_reason` (min 10 chars after trim).
The acknowledgement is audit-logged via three channels: (1) `activity_log.action =
'order_delinquency_acknowledged'`, (2) `order_timeline` entry, and (3)
`order.delinquency_acknowledged` realtime broadcast. The order records
`orders.delinquency_acknowledged_at / _by / _reason` for the controller report surface.
Industry: NetSuite Customer Has Past Due Invoices override, Acumatica Past Due
workflow override Modal, Brightpearl override-credit-limit prompt with reason field.

Requires `orders:write` scope. Override path additionally requires the
`accounts.override_credit_hold` permission. Delinquency path requires the
`accounts.acknowledge_delinquency` permission (returns HTTP 403 `permission_denied`
if the API key holder lacks either).




## OpenAPI

````yaml /openapi.yaml post /orders/{id}/confirm
openapi: 3.1.0
info:
  title: Arcus ERP Public API
  version: 1.0.0
  description: >
    Arcus ERP public REST API. Designed for external integrations and data
    migration.


    **Authentication.** Bearer token (API key) via the `Authorization` header.

    Format: `Authorization: Bearer ark_live_ent_<code>_<random>` (or
    `ark_test_*` for sandbox).

    API keys are issued per-entity in **Settings > Developers > API Keys**.


    **Entity scoping.** The entity is encoded in the API key prefix; routes are
    flat

    (e.g. `/v1/accounts`, `/v1/orders`, `/v1/products`). A small set of platform
    endpoints

    (migration, reconciliation, events, webhook endpoints, API keys) use the

    `/v1/entities/{entity_id}/...` form -- those are noted in their tags.


    **Key capabilities.**
      - Related-resource hydration via `?expand[]=` (see `x-arcus-expand` on each resource).
      - Cursor-based pagination (`starting_after` / `ending_before` / `limit`).
      - Idempotency via the `Idempotency-Key` header.
      - Webhook events for asynchronous notification.
      - Conditional requests / ETag for cache validation.
servers:
  - url: https://api.arcuserp.com/v1
    description: Arcus ERP API (accepts both live `ark_live_*` and test `ark_test_*` keys)
  - url: https://dev-api.arcuserp.com/v1
    description: >-
      Dev sandbox API (test-only data, accepts `ark_test_*` keys against dev
      RDS)
security: []
paths:
  /orders/{id}/confirm:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags:
        - Orders
      summary: Confirm a draft sales order or invoice into the OPEN state
      description: >
        Transitions a draft sales order or draft invoice into the OPEN state,
        runs the

        approval-threshold + credit-hold + delinquency gates, and triggers
        inventory allocation

        for every line item. The order must have at least one line item and a
        valid shipping

        address. Confirmation generates an order-confirmed activity log entry
        and optionally

        sends a confirmation email when `send_email: true` is passed.


        **Quotes cannot be confirmed.** A document with `document_type='quote'`
        returns HTTP 422

        with `code: 'quote_must_convert_before_confirm'`. The canonical
        quote-to-sales-order

        doorway is `POST /orders/{id}/convert`; after conversion, call
        `/confirm` on the

        resulting sales order. This mirrors NetSuite Estimate, Acumatica Sales
        Quote, Odoo

        Quotation, and QuickBooks Estimate behavior (quotes do not commit
        inventory).


        **Credit hold override
        (NEW-GAP-CS-CREDIT-HOLD-OVERRIDE-WITH-PERMISSION-AND-AUDIT,

        2026-05-16).** When an account is on credit hold (manual flag or
        credit-limit

        exceeded), confirmOrder normally returns HTTP 409 with `error: 'Order
        placed on

        hold'` and places the order in `is_on_hold = true`. An operator with the

        `accounts.override_credit_hold` permission can push the order through
        the gate

        by passing `override_credit_hold: true` AND `override_reason` (minimum
        10 chars

        after trim) in the request body. The override is audit-logged via three
        channels:

        (1) `activity_log.action = 'order_credit_hold_overridden'`, (2)
        `order_timeline`

        entry, and (3) `order.credit_hold_overridden` realtime broadcast. The
        order also

        records `orders.credit_hold_override_at / _by / _reason / _type` for
        reporting.

        Industry: NetSuite "Override Credit Hold" right, Acumatica
        `SO.OverrideHold`,

        Brightpearl override-credit-limit prompt.


        **Delinquency acknowledgement
        (NEW-GAP-DELINQUENCY-ACKNOWLEDGE-FE-MODAL-AND-OPENAPI,

        2026-05-18).** When an account has overdue invoices (but is not on hard
        credit hold),

        confirmOrder returns HTTP 422 `requires_delinquency_acknowledgment` with

        `overdue_count`, `overdue_amount`, and `max_days_overdue`. An operator
        with the

        `accounts.acknowledge_delinquency` permission can push through by
        retrying with

        `acknowledge_delinquency: true` AND `acknowledge_reason` (min 10 chars
        after trim).

        The acknowledgement is audit-logged via three channels: (1)
        `activity_log.action =

        'order_delinquency_acknowledged'`, (2) `order_timeline` entry, and (3)

        `order.delinquency_acknowledged` realtime broadcast. The order records

        `orders.delinquency_acknowledged_at / _by / _reason` for the controller
        report surface.

        Industry: NetSuite Customer Has Past Due Invoices override, Acumatica
        Past Due

        workflow override Modal, Brightpearl override-credit-limit prompt with
        reason field.


        Requires `orders:write` scope. Override path additionally requires the

        `accounts.override_credit_hold` permission. Delinquency path requires
        the

        `accounts.acknowledge_delinquency` permission (returns HTTP 403
        `permission_denied`

        if the API key holder lacks either).
      operationId: confirmOrder
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                acknowledge_delinquency:
                  type: boolean
                  description: >
                    Set to `true` to bypass the delinquency soft-warning
                    (account has

                    overdue invoices but is not on manual / credit-limit hold).
                    Must be

                    paired with `acknowledge_reason` (min 10 chars after trim).
                    Requires

                    `accounts.acknowledge_delinquency` permission (HTTP 403 if
                    denied).

                    Industry: NetSuite Customer Past-Due Override, Acumatica
                    Past Due

                    workflow override, Brightpearl override-credit-limit prompt.
                acknowledge_reason:
                  type: string
                  minLength: 10
                  description: >
                    Operator-typed reason for proceeding despite delinquency.
                    Required

                    when `acknowledge_delinquency: true`. Minimum 10 characters
                    after

                    trim. Stored in `orders.delinquency_acknowledged_reason` and

                    emitted in the `order_timeline` + `activity_log` row so AR
                    teams

                    can review via the controller audit report surface. Returns
                    HTTP

                    422 `delinquency_acknowledge_requires_reason` if omitted or
                    too short.
                  example: >-
                    Customer confirmed ACH payment of overdue invoices en route;
                    GM approved.
                override_credit_hold:
                  type: boolean
                  description: >
                    Set to `true` to push an on-hold order through the credit
                    gate

                    WITHOUT placing it on hold. Requires
                    `accounts.override_credit_hold`

                    permission AND a paired `override_reason` (min 10 chars
                    after trim).

                    Audit-logged via activity_log + order_timeline + realtime
                    broadcast.
                override_reason:
                  type: string
                  minLength: 10
                  description: >
                    Operator-typed reason for the credit-hold override. Required
                    when

                    `override_credit_hold: true`. Stored in

                    `orders.credit_hold_override_reason` + audit log metadata.
                  example: >-
                    GM John approved by phone; card on file pre-charged for full
                    balance.
                send_email:
                  type: boolean
                  description: Send the order confirmation email on success.
      responses:
        '200':
          description: Confirmed order
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '400':
          $ref: '#/components/responses/Error'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          description: >
            Two permission gates can return 403 on this endpoint:
              (a) Operator lacks `accounts.override_credit_hold` while attempting
                  a credit-hold override push-through (`permission_key: accounts.override_credit_hold`).
              (b) Operator lacks `accounts.acknowledge_delinquency` while attempting
                  a delinquency acknowledgement (`permission_key: accounts.acknowledge_delinquency`).
            Both are Pattern A envelopes. The `permission_key` field identifies
            which

            gate fired. Roles that carry each permission by default:
              - `accounts.override_credit_hold`: Owner, Admin
              - `accounts.acknowledge_delinquency`: Owner, Admin, Manager, Accountant
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: permission_denied
                  code:
                    type: string
                    example: permission_denied
                  type:
                    type: string
                    example: permission_error
                  permission_key:
                    type: string
                    enum:
                      - accounts.override_credit_hold
                      - accounts.acknowledge_delinquency
                    example: accounts.acknowledge_delinquency
                  hint:
                    type: string
                    example: >-
                      Acknowledging a delinquent account requires the
                      'accounts.acknowledge_delinquency' permission. Ask an
                      Owner, Admin, Manager, or Accountant.
        '404':
          $ref: '#/components/responses/Error'
        '409':
          description: >
            Order placed on credit hold (account on manual hold or new order
            pushes

            outstanding balance past credit_limit). Response includes the
            canonical

            override hint pointing at the `accounts.override_credit_hold`
            permission

            and the body params an authorized operator can retry with.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Order placed on hold
                  hold:
                    type: boolean
                    example: true
                  hold_type:
                    type: string
                    enum:
                      - manual
                      - credit_limit
                    example: credit_limit
                  reason:
                    type: string
                    example: >-
                      Order exceeds credit limit. Outstanding: $4,200.00 .
                      Limit: $5,000.00 . This order: $850.00
                  override_permission_key:
                    type: string
                    example: accounts.override_credit_hold
                  override_hint:
                    type: string
                    example: >-
                      A user with accounts.override_credit_hold may push this
                      through by retrying with body.override_credit_hold=true
                      and body.override_reason (min 10 chars).
        '422':
          description: |
            Three cases share this envelope class. All are Pattern A envelopes.
              (a) Confirmation refused because the document is a quote
                  (`code: quote_must_convert_before_confirm`).
              (b) Credit-hold override push-through attempted without a valid
                  `override_reason` (min 10 chars; `code: credit_hold_override_requires_reason`).
              (c) Delinquency acknowledgement attempted without a valid
                  `acknowledge_reason` (min 10 chars; `code: delinquency_acknowledge_requires_reason`).
                  The response also includes `overdue_count`, `overdue_amount`, and
                  `max_days_overdue` so the UI can re-surface the delinquency context.
              (d) Initial delinquency soft-warn before the operator has passed
                  `acknowledge_delinquency: true` (`error: requires_delinquency_acknowledgment`).
                  Includes `overdue_count`, `overdue_amount`, `max_days_overdue`,
                  `acknowledge_permission_key`, and `acknowledge_hint`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: delinquency_acknowledge_requires_reason
                  code:
                    type: string
                    example: delinquency_acknowledge_requires_reason
                  type:
                    type: string
                    example: validation_error
                  hint:
                    type: string
                    example: >-
                      Provide a reason >= 10 characters explaining the
                      acknowledgement.
                  overdue_count:
                    type: integer
                    description: >-
                      Present on delinquency cases (cases c and d). Number of
                      overdue invoices.
                    example: 3
                  overdue_amount:
                    type: number
                    description: >-
                      Present on delinquency cases (cases c and d). Total
                      overdue amount in dollars.
                    example: 4750
                  max_days_overdue:
                    type: integer
                    description: >-
                      Present on delinquency cases (cases c and d). Age of
                      oldest overdue invoice in days.
                    example: 47
                  acknowledge_permission_key:
                    type: string
                    description: >-
                      Present on case d (initial warn). The permission key
                      required to acknowledge.
                    example: accounts.acknowledge_delinquency
                  acknowledge_hint:
                    type: string
                    description: >-
                      Present on case d (initial warn). Human-readable
                      instruction.
                    example: >-
                      A user with accounts.acknowledge_delinquency may push
                      through by retrying with acknowledge_delinquency=true and
                      acknowledge_reason (min 10 chars).
                  hold_type:
                    type: string
                    enum:
                      - manual
                      - credit_limit
                    description: Present on case b (credit-hold override).
                    example: credit_limit
                  original_reason:
                    type: string
                    description: Present on case b (credit-hold override).
                    example: >-
                      Order exceeds credit limit. Outstanding: $4,200.00 .
                      Limit: $5,000.00 . This order: $850.00
                  convert_endpoint:
                    type: string
                    description: Present on the quote-refusal case (a) only.
                    example: /orders/0c2a8a8e-8a8a-4a8a-8a8a-8a8a8a8a8a8a/convert
      security:
        - ApiKeyAuth: []
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        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.
      schema:
        type: string
        maxLength: 255
  schemas:
    Order:
      type: object
      description: >
        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).
      properties:
        id:
          type: string
          format: uuid
        object:
          type: string
          enum:
            - order
        entity_id:
          type: string
          format: uuid
          readOnly: true
        order_number:
          type: string
          readOnly: true
        document_type:
          type: string
          enum:
            - quote
            - sales_order
            - invoice
            - return
            - purchase_order
        order_status:
          type: string
          enum:
            - draft
            - confirmed
            - partially_fulfilled
            - fulfilled
            - cancelled
            - voided
            - awaiting_ach_clearance
            - on_hold
        payment_status:
          type: string
          enum:
            - unpaid
            - partially_paid
            - paid
            - overpaid
            - refunded
            - partially_refunded
            - voided
        fulfillment_status:
          type: string
          enum:
            - unfulfilled
            - partially_fulfilled
            - fulfilled
        account_id:
          type: string
          format: uuid
          nullable: true
        location_id:
          type: string
          format: uuid
          nullable: true
        po_number:
          type: string
          nullable: true
        order_date:
          type: string
          format: date-time
          nullable: true
        due_date:
          type: string
          format: date-time
          nullable: true
        subtotal:
          type: number
        discount_total:
          type: number
        shipping_total:
          type: number
        tax_total:
          type: number
        fee_total:
          type: number
        order_total:
          type: number
        list_price_total:
          type: number
          readOnly: true
          description: >-
            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.
        pricing_savings:
          type: number
          readOnly: true
          description: >-
            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.
        amount_paid:
          type: number
          readOnly: true
          description: 'SSOT: utils/ar-helpers.mjs::updateARBalance. Do not write directly.'
        balance_due:
          type: number
          readOnly: true
        tax_exempt:
          type: boolean
        delivery_option:
          type: string
          enum:
            - ship
            - pick_up
            - local_delivery
        notes:
          type: string
          nullable: true
        internal_notes:
          type: string
          nullable: true
        invoice_number:
          type: string
          nullable: true
          readOnly: true
        invoice_type:
          type: string
          enum:
            - order
            - manual
            - proforma
            - correction
          nullable: true
        is_on_hold:
          type: boolean
          readOnly: true
        hold_type:
          type: string
          nullable: true
          readOnly: true
        ship_complete:
          type: boolean
        source_platform:
          type: string
          nullable: true
        external_order_id:
          type: string
          nullable: true
        external_order_number:
          type: string
          nullable: true
        metadata:
          type: object
          nullable: true
        created_at:
          type: string
          format: date-time
          readOnly: true
        updated_at:
          type: string
          format: date-time
          readOnly: true
    ErrorEnvelope:
      type: object
      description: |
        Canonical error response envelope. All API errors use this shape.
      required:
        - error
        - code
      properties:
        error:
          type: string
          description: Machine-readable error key
          example: not_found
        code:
          type: string
          description: Machine-readable error code (often same as error)
          example: not_found
        type:
          type: string
          enum:
            - validation_error
            - permission_error
            - not_found
            - conflict
            - rate_limit
            - internal
            - expand_error
            - not_implemented
          example: not_found
        hint:
          type: string
          description: Human-readable one-sentence explanation (English)
          example: >-
            The requested order does not exist or does not belong to this
            entity.
        param:
          type: string
          description: The parameter that caused the error, if applicable
          example: expand[0]
        required:
          type: string
          description: The scope required (only on insufficient_scope errors)
          example: accounts:read
        request_id:
          type: string
          description: >-
            Unique request ID for support tracing (maps to CloudWatch log
            stream)
          example: req_abc123
  responses:
    Error:
      description: Error response (400/401/403/404/409/422/429/500)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: |
        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_<code>_<random>
        Test keys use ark_test_ent_<code>_<random>. Both are issued per entity
        via Settings > Developers > API Keys.

````