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

# Replace a vendor bill's lines

> Replaces the bill's lines in one atomic supersession through the same door the app uses: its landed cost is reversed and re-allocated, the landed warnings and the duplicate acknowledgement apply as on create, and a receipt set on a bill that names a purchase order is refused 422 landed_receipt_set_with_po. The acting user is the API key's owner. The landed cost figures follow the margin permission (an API key receives the rest without them).



## OpenAPI

````yaml /openapi.yaml post /vendor-bills/{id}/edit-lines
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.

    **Changelog.** Entries are dated and name every published contract whose
    MEANING moved, not

    only the ones whose field names changed. The narrative version of the same
    entries, written

    for integrators, is published at https://arcuserp.mintlify.app/changelog.


    **2026-09-22 (planned 2026-09-21), REORDER-BUYER-TRUTH: demand changed what
    it MEANS on three

    published contracts, with no field renamed.** An integrator that pins field
    names sees no

    breakage and different numbers, which is why this entry exists.

      - **Demand now counts build consumption.** `demand_avg_per_day` on the public product, kit
        and inventory-balance objects, and `daily_demand` / `demand_basis` / `net_suggested_qty`
        on `GET /v1/purchasing/reorder-report`, are composed from fulfilled sales lines PLUS
        posted `build_consume` inventory draws: each physical decrement of a product counts
        exactly once. The previous rule adopted an internal-consumption basis only when the sales
        blend was exactly zero, so a product both sold AND consumed into work orders planned as
        if the build draws did not exist. Products drawn into work orders move; on one
        production-shaped dataset five did, the largest from 0.05/day to 14.95/day.
      - **`demand_basis` now carries four values, not two:** `sales`, `sales_and_builds`,
        `builds` and `consumption`. A consumer with a two-branch reader (anything that is not
        `consumption` is `sales`) silently hides the two new ones.
      - **`current_demand_units` on `GET /v1/inventory/balances/:id` moved, by a second rule.**
        It counts committed-but-unshipped CUSTOMER demand, and it now counts a kit component's
        own line rather than its parent kit line, and excludes non-sales documents. On one
        production dataset 269 of 1,043 balance rows changed, 227 of them downward; the largest
        single move was 1,941 to 25, on a product whose open PURCHASE order line had been
        reported as customer demand. Re-baseline anything that alerts or reorders off this field.
      - `run_rate_30/90/180/365` and `blended_daily` on the same balance object move for the
        first reason above.
      - **Added, not changed:** `GET /v1/purchasing/reorder-report` accepts `demand_window` and
        returns the unit, cover-through and reorder-point-provenance fields documented on that
        operation; `order_multiple` is accepted and returned on the product-vendor doors.
      - **Volume note.** The `inventory.low_stock`, `inventory.out_of_stock` and
        `inventory.back_in_stock` webhook events are population-gated on
        `on_hand <= reorder_point`, and reorder points move with the demand above, so
        subscribers should expect a one-time step change in event volume around the release.
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:
  /vendor-bills/{id}/edit-lines:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: Idempotency-Key
        in: header
        required: false
        schema:
          type: string
        description: >-
          Optional idempotency key (UUID). Re-POST with same key returns cached
          200 without re-processing.
    post:
      tags:
        - Purchasing
      summary: Replace a vendor bill's lines
      description: >-
        Replaces the bill's lines in one atomic supersession through the same
        door the app uses: its landed cost is reversed and re-allocated, the
        landed warnings and the duplicate acknowledgement apply as on create,
        and a receipt set on a bill that names a purchase order is refused 422
        landed_receipt_set_with_po. The acting user is the API key's owner. The
        landed cost figures follow the margin permission (an API key receives
        the rest without them).
      operationId: editVendorBillLinesV1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - items
              properties:
                items:
                  type: array
                  description: The full desired line set for the bill.
                  items:
                    $ref: '#/components/schemas/VendorBillLineInput'
                landed_cost_receipt_txn_ids:
                  type: array
                  nullable: true
                  items:
                    type: string
                    format: uuid
                  description: >-
                    Replaces the bill's receipt set; when absent (or null) the
                    stored set is kept and re-judged.
                allow_duplicate_override:
                  type: boolean
                  description: >-
                    Overrides a duplicate refusal, with
                    duplicate_override_reason (see createVendorBillV1); audited.
                duplicate_override_reason:
                  type: string
                  description: >-
                    Why the duplicate is a separate charge (required with
                    allow_duplicate_override).
      responses:
        '200':
          description: The bill as edited.
        '400':
          $ref: '#/components/responses/Error'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
        '404':
          $ref: '#/components/responses/Error'
        '409':
          $ref: '#/components/responses/Error'
        '422':
          $ref: '#/components/responses/Error'
      security:
        - ApiKeyAuth: []
components:
  schemas:
    VendorBillLineInput:
      description: >-
        One line of a vendor bill as a write door reads it: createVendorBillV1,
        correctBillLandedCostV1 and editVendorBillLinesV1 all take an array of
        these (one schema, so a client, the Postman collection and the Arcus AI
        action schema see the same fields on every door).
      type: object
      required:
        - description
        - amount
      properties:
        description:
          type: string
        amount:
          type: number
          description: >-
            Signed line amount. A POSITIVE amount debits the line's account; a
            NEGATIVE amount CREDITS it (a contra line, e.g. a payroll
            withholding on a bill to a payroll provider). Accounts Payable is
            credited the ALGEBRAIC NET of all lines plus header tax and
            shipping, and that net must be POSITIVE -- a document that nets to
            zero or less is a vendor credit, not a bill (POST
            /v1/vendor-credits). A negative amount is only legal on a line that
            posts to its own expense account: it is refused on a PO-linked line
            (negative_line_not_allowed_on_inventory_line), a product line
            (same), a landed-cost line
            (negative_line_not_allowed_on_landed_line), a line with no
            gl_account_id (negative_line_requires_gl_account), and a line
            carrying tax (negative_line_tax_not_supported, phase 1). A negative
            QUANTITY is always refused (negative_quantity_not_allowed) -- use a
            vendor return. Negative header tax_total / shipping_total are
            refused (header_amount_cannot_be_negative). A line whose
            gl_account_id is the Accounts Payable or Accounts Receivable control
            account, the GRNI accrual, the landed-cost clearing account, or a
            bank account is refused at posting time
            (bill_line_on_control_account), either sign. Line amounts resolve as
            amount, else (quantity or 1) x unit_cost; a quantity of 0 or null is
            treated as 1.
        product_id:
          type: string
          format: uuid
        quantity:
          type: number
        unit_cost:
          type: number
        tax_rate:
          type: number
        po_item_id:
          type: string
          format: uuid
          description: >-
            The purchase-order line this goods line bills. Every PO-linked line
            must belong to the bill's own po_id (refused 422
            multi_po_bill_not_supported otherwise, on every bill door).
        tax_amount:
          type: number
          description: >-
            The line's tax in currency. When any line carries tax_rate or
            tax_amount the bill's header tax_total is derived from its lines and
            a tax_total in the body is ignored.
        gl_account_id:
          type: string
          format: uuid
        line_kind:
          type: string
          enum:
            - goods
            - expense
            - landed
          description: >-
            'landed' capitalizes the line (freight, duty, brokerage, insurance,
            handling) into the received FIFO layers it lands on when the entity
            has landed_cost_capitalization_enabled; otherwise it expenses to its
            own account.
        landed_cost_category:
          type: string
          description: >-
            The landed charge's category (freight_in, duty, brokerage,
            insurance, handling, or an entity-configured one).
        landed_alloc_basis:
          type: string
          enum:
            - value
            - quantity
            - weight
            - volume
            - equal
            - manual
          description: >-
            How a landed line spreads over the layers it lands on. 'value'
            weighs each layer by its received quantity times its RECEIPT cost
            (before any other landed bill's bump), so the order two landed bills
            arrive in never changes the split. 'manual' requires
            landed_manual_shares.
        landed_manual_shares:
          type: array
          description: >-
            Required when landed_alloc_basis is 'manual' (refused 422
            landed_manual_shares_required without it): the amount per receipt
            line, for example the duty a customs entry states per HTS line. The
            amounts must add up to the line to the cent (422
            landed_manual_shares_do_not_sum), name each receipt once, and name
            only receipts the bill lands on (422
            landed_manual_share_not_on_this_bill). Ignored (cleared) on any
            other basis.
          items:
            type: object
            required:
              - receiving_transaction_id
              - amount
            properties:
              receiving_transaction_id:
                type: string
                format: uuid
                description: >-
                  The purchase receipt (inventory transaction) the amount lands
                  on.
              amount:
                type: number
                minimum: 0
    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.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.