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

# Correct a bill's landed cost

> Corrects the landed cost on a paid or partially paid vendor bill by reversing the prior landed-cost allocation, re-allocating at the corrected freight amount, and issuing a vendor credit for the overcharge. Requires `purchasing:write` scope plus the per-user `accounting.post` permission, is idempotent via the `Idempotency-Key` header, and is refused with 409 when the bill has been voided, written off, or has no payments applied yet.



## OpenAPI

````yaml /openapi.yaml post /vendor-bills/{id}/correct-landed-cost
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}/correct-landed-cost:
    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: Correct a bill's landed cost
      description: >-
        Corrects the landed cost on a paid or partially paid vendor bill by
        reversing the prior landed-cost allocation, re-allocating at the
        corrected freight amount, and issuing a vendor credit for the
        overcharge. Requires `purchasing:write` scope plus the per-user
        `accounting.post` permission, is idempotent via the `Idempotency-Key`
        header, and is refused with 409 when the bill has been voided, written
        off, or has no payments applied yet.
      operationId: correctBillLandedCostV1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - items
              properties:
                items:
                  type: array
                  description: >-
                    The full desired line set for the bill, carrying the
                    corrected landed amount (landed lines may use
                    landed_alloc_basis 'manual' with landed_manual_shares, as on
                    create).
                  items:
                    $ref: '#/components/schemas/VendorBillLineInput'
                landed_cost_receipt_txn_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: >-
                    Replaces the bill's receipt set; when absent the stored set
                    is kept and re-judged.
                allow_duplicate_override:
                  type: boolean
                  description: >-
                    Overrides a likely duplicate landed charge, with
                    duplicate_override_reason (see createVendorBillV1); audited.
                duplicate_override_reason:
                  type: string
      responses:
        '200':
          description: >-
            Landed cost corrected; vendor credit issued. The body carries
            landed_reversal (the reversal's split between inventory and cost of
            goods sold at the moment it ran), landed_allocation (the
            re-allocation and the basis each landed line used) and
            landed_receipt_warnings (every other landed bill on the receipts).
            The cost figures (the split, the portions,
            allocated_on_these_receipts) follow the margin permission, so an API
            key receives the rest without them.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      landed_receipt_warnings:
                        type: array
                        items:
                          $ref: '#/components/schemas/LandedReceiptWarning'
                      landed_allocation:
                        $ref: '#/components/schemas/LandedAllocation'
                      landed_reversal:
                        type: object
                        nullable: true
        '400':
          $ref: '#/components/responses/Error'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
        '404':
          $ref: '#/components/responses/Error'
        '409':
          description: >-
            The correction cannot be made as sent; nothing is posted.
            landed_duplicate_charge (a likely duplicate landed charge on the
            receipts: the candidates, the landed warnings and requires_override;
            resend with allow_duplicate_override and duplicate_override_reason
            if it is a separate charge) answers with LandedDuplicateChargeError.
            Every other code answers with the standard error envelope:
            bill_not_paid, no_landed_allocation,
            landed_reversal_units_in_transit,
            landed_reversal_units_left_the_company, landed_reversal_units_held,
            landed_reversal_units_untraceable (the last four with meta.blocks
            and meta.holds naming each) and landed_reversal_stock_moved (stock
            that carries the charge moved while the correction was starting;
            retry).
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/LandedDuplicateChargeError'
                  - $ref: '#/components/schemas/ErrorEnvelope'
        '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
    LandedReceiptWarning:
      type: object
      description: >-
        A landed bill already on the receipts a landed bill lands on (a warning,
        never a refusal).
      properties:
        bill_id:
          type: string
          format: uuid
        bill_number:
          type: string
        vendor_id:
          type: string
          format: uuid
        vendor_name:
          type: string
          nullable: true
        bill_date:
          type: string
          format: date
        status:
          type: string
        receipt_ids:
          type: array
          items:
            type: string
            format: uuid
        categories:
          type: array
          items:
            type: string
        landed_amount:
          type: number
        allocated_on_these_receipts:
          type: number
          description: >-
            What that bill allocated on these receipts. Omitted, on every door
            (create, correct, approve, the 409 landed_duplicate_charge and the
            receipt picker), for a caller without the margin permission
            (orders.view_margin); no API scope grants it today, so an API key
            never receives it.
        allocated_on_this_receipt:
          type: number
          description: >-
            On a landed receipt picker row: that bill's allocation over THIS
            receipt (allocated_on_these_receipts carries the same number on a
            row; the doors' warning list keeps the set-wide figure). Omitted
            without the margin permission.
        landed_lines:
          type: array
          items:
            type: object
            properties:
              category:
                type: string
              amount:
                type: number
        sentence:
          type: string
    LandedAllocation:
      type: object
      description: >-
        What a landed allocation posted and the basis each landed line actually
        used (a fallback is named). The inventory and COGS portions are omitted
        for a caller without the margin permission (every API key today).
      properties:
        journal_entry_id:
          type: string
          format: uuid
        landed_total_cents:
          type: integer
        inventory_portion_cents:
          type: integer
          description: Omitted without the margin permission.
        cogs_portion_cents:
          type: integer
          description: Omitted without the margin permission.
        allocation_mode:
          type: string
          enum:
            - receipt_set
            - bill_lines
            - po_layers
        allocation_bases:
          type: array
          items:
            type: object
            properties:
              item_id:
                type: string
                format: uuid
              requested_basis:
                type: string
              basis_used:
                type: string
              fell_back:
                type: boolean
    LandedDuplicateChargeError:
      type: object
      description: >-
        409 landed_duplicate_charge, a likely duplicate landed charge; resend
        with allow_duplicate_override and duplicate_override_reason if it is a
        separate charge.
      properties:
        error:
          type: string
        code:
          type: string
        hint:
          type: string
        duplicates:
          type: array
          items:
            type: object
            properties:
              bill_id:
                type: string
                format: uuid
              bill_number:
                type: string
              vendor_name:
                type: string
                nullable: true
              bill_date:
                type: string
                format: date
              category:
                type: string
              existing_amount:
                type: number
              new_amount:
                type: number
        landed_receipt_warnings:
          type: array
          items:
            $ref: '#/components/schemas/LandedReceiptWarning'
        requires_override:
          type: boolean
    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.