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

# Un-receive a receipt

> Takes units of one receipt back out of Holding, as if they never arrived: the receipt items and quantities in `items`, or, without `items`, every unit of the receipt that is not put away. Units already put away are refused (422 `units_already_decided`: undo that put-away first), and so is a return that settled money until its settlement is re-opened (409 `return_money_settled`). A serial-tracked item names the serials leaving. When nothing on the return stays received it goes back to `authorized`; a receipt that recorded value on receipt has that value taken back (`unreceive.journal_entry_ids`). `reason` is kept as a note on the return, and the act is written on its activity under the key's creator. A request with a `request_key` (else `client_request_id`, else the `Idempotency-Key` header) is one act: a retry answers the first act and moves nothing. Preview it with `GET /returns/{id}/receipts/{receipt_id}/unreceive-preview`. Requires the `returns:write` scope and, for the key's creator, the `returns.unreceive` permission (`returns.receive` is enough when every unit taken is still in Holding on the return's own stock).



## OpenAPI

````yaml /openapi.yaml post /returns/{id}/receipts/{receipt_id}/unreceive
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.


    **October 2026: the return list's location filter reads a return's own
    return location.** `GET /v1/returns` with

    `X-Location-Id` (or `location_id`) scopes a return by its own return
    location when one is set, else by its

    originating order's location, the way the returns list in the app scopes it:
    a return whose goods come back to

    another warehouse than its order shipped from is listed at that warehouse
    and no longer at the order's. A return

    with neither stays visible at every location. The Return now documents
    `return_location_id` and

    `return_location_source`, which every return read already answered, and
    `original_order_id` as nullable (a return

    that names no order).


    **October 2026: the adjustment and location pages name the refusals of the
    locations the returns own.**

    `POST /v1/inventory/adjustments` and `POST /v1/adjustments` (adjustment_type
    inventory) name what they answer at the

    entity's system Holding or Write-off: 422 `location_owned_by_documents` for
    a decrease (`owner`, `location`, `holders`

    and `units_held` name the returns that hold the product there), 422
    `system_location_note_required` for an increase

    without a reason and a note, and 422
    `system_location_override_reason_required` and 403

    `system_location_override_requires_user` for `override` (adjusting anyway is
    a signed-in person's act, never an API

    key's). `POST /v1/locations` names 422 `system_location_exists` (a second
    Holding), and `PATCH /v1/locations/{id}`

    names 422 `system_location_exists` (a second Holding or Write-off),
    `system_location_type_fixed` and

    `holding_location_holds_units`. A new entity's default adjustment reasons
    damage, writeoff and other require a

    note (`adjustment_note_required`). The operations already answered these;
    the pages now say so.


    **October 2026: an API key writes on a return only a note it can read
    back.** `POST /v1/notes` on a return or a

    return line (`parent_type` return or return_item) answers 422
    `note_internal_on_return` unless the body sends

    `is_internal` false: a body that left it out used to write an internal note,
    which no API key can list, read, edit

    or delete. `PATCH /v1/notes/{id}` answers the same refusal for `is_internal`
    sent as anything but false on a note of a

    return or a return line. Notes on every other parent are unchanged. The
    create's request body names `content`, the

    field it always read (it listed `body`), and its 201 answer is the note
    itself; the `Note` schema no longer lists

    `object`, which no note answer carries.


    **October 2026: a return read with an API key names no member of staff.**
    `GET /v1/returns/{id}` and the operations

    that answer the return as it reads (`POST /v1/returns/{id}/receive`,
    `/inspect`, `/disposition`,

    `/receive-and-dispose`, `/unreceive`, `/receipts/{receipt_id}/unreceive`,

    `/receipt-items/{receipt_item_id}/repair-stock` and `/return-location`) no
    longer answer `voided_by_name`, the name of

    the person who voided the return, which no page of this reference listed. An
    answer replayed for an

    `Idempotency-Key` leaves it out too. Nothing else in these answers changes.


    **October 2026: a return's journal entries, and which of them were reversed,
    on the public API.**

    `GET /v1/journal-entries/expand` takes `for_return`, a return's id: the rows
    are every journal entry the return caused

    and every reversal of any of them, oldest first, and each row answers
    `reversed_by` (the `id`, `entry_number` and

    `entry_date` of the entry that reversed it, or null) and `reversed_at` (when
    that entry posted, or null). A value that

    is not a UUID is refused 400 `invalid_id_format`. The page now documents its
    rows as they are answered: journal entry

    headers without their lines, and the list's `next_cursor` and `url`.


    **October 2026: a return says what the restocking fee bounds held.** `GET
    /v1/returns/{id}` answers `restocking_bound`

    when the minimum or maximum restocking fee (Settings > Returns) holds the
    return's computed restocking fee: `code`

    (`restocking_fee_above_entity_max` or `restocking_fee_below_entity_min`),
    `entity_fee_min`, `entity_fee_max`, the fee

    before the bound (`from`) and after it (`to`), `source`, and `words`, the
    sentence to show, for example "Restocking fee

    $15.00, limited to the entity maximum of $10.00 (Settings > Returns)." It is
    absent when the bounds hold nothing. A fee

    line of `POST /v1/returns/{id}/fee-intents` carries the same object. The
    page of `GET /v1/returns/{id}` now documents its

    answer as it is given: the return under `data`.


    **October 2026: a return's receipt is previewed, undone and repaired, and
    its location changed, on the public API.**

    `GET /v1/returns/{id}/receive-preview` shows where a receipt by product
    would place its units and every refusal the

    receive would answer for it, writing nothing. `POST
    /v1/returns/{id}/receipts/{receipt_id}/unreceive` takes units of

    one receipt back out of Holding (its `items`, or every unit of it not put
    away) and

    `GET /v1/returns/{id}/receipts/{receipt_id}/unreceive-preview` shows what it
    would take back (`items`, `materialized`,

    `receive_key_admits`, `journal_entries`); `POST /v1/returns/{id}/unreceive`
    does it for every receipt of the return.

    Each takes `reason` (10 characters or more) and `request_key` (a retry
    answers the first act; another request under

    the key is refused 409 `request_key_reused`), refuses units already put away
    (422 `units_already_decided`, or

    `disposition_present`) and units no longer in Holding (422
    `units_not_in_holding`), and answers 409

    `unreceive_conflict_retry` when another change to the same stock was saved
    at the same moment.

    `POST /v1/returns/{id}/receipt-items/{receipt_item_id}/repair-stock`
    resolves a receipt recorded without stock

    (`mode` `book_missing_stock` or `reverse_unbacked`, with a count `note`, 422
    `count_note_required`), and

    `POST /v1/returns/{id}/return-location` sets the warehouse the goods come
    back to (`location_id`, and a `reason` once

    goods are received; 422 `return_location_not_a_warehouse`, 409
    `return_not_open` and `return_location_read_only`).

    The writes take the `returns:write` scope and the `Idempotency-Key` header;
    the previews `returns:read`.


    **October 2026: the receipt, grade and put-away pages of a return say what
    the goods steps answer.**

    `POST /v1/returns/{id}/receive` books every unit into the entity's Holding
    location (`receiving_location_id` names the

    warehouse the goods arrived at); an entity without exactly one active
    Holding location is refused 422

    `holding_location_missing`, and `no_receiving_location` is no longer given,
    here or on

    `POST /v1/returns/{id}/receive-and-dispose`. The receive takes a receipt by
    product (`by_product`, with `split`,

    `split_reason`, `allocation_note_id`, `serial_numbers` and `entered_costs`),
    a line's `serial_numbers` and

    `entered_cost`, and `client_request_id`; a further lot on a return that
    already holds received units is refused 400

    `rma_already_received` only when it carries no `client_request_id` and no
    `Idempotency-Key`. Both receive operations

    name 409 `return_order_set_changed`: the orders the return touches changed
    while it was being locked, and nothing was

    written. `POST /v1/returns/{id}/receive-and-dispose` takes `force_restock`
    with `force_restock_reason` (422

    `force_restock_reason_required` without the reason). `POST
    /v1/returns/{id}/inspect`, `/disposition` and

    `/quarantine/resolve` document the grade, put-away and hold bodies they read
    and every refusal they answer, and

    `POST /v1/returns/{id}/exchange` and `/advance-replacement` name 422
    `return_has_no_account` and

    `tax_exempt_requires_exemption_code`.


    **October 2026: a return's reason, and a line's, change on the public API.**
    `PATCH /v1/returns/{id}` takes

    `reason` (or `reason_code`), one of the reason codes, and `reason_words`:
    the lines that carried the return's

    reason follow it, the words are kept as an internal note of the return, and
    while the return is open its

    automatic fees follow; the answer carries `reason_change`. `PATCH
    /v1/returns/{id}/items/{item_id}` (scope

    `returns:write`, the `Idempotency-Key` header honoured) changes one line's
    `reason_code` the same way. A reason that

    is not one of the codes is refused 422 `invalid_reason_code`, the reason
    `other` with nothing saying why 422

    `reason_words_required`, words over 2,000 characters 422 `note_too_long`,
    and a change once a credit, a debit, a

    credit memo or a refund of the return is issued 409
    `reason_locked_after_money` (`meta.issued` names each).


    **October 2026: a return's fees change one at a time, and the update's fee
    array is deprecated.**

    `POST /v1/returns/{id}/fee-intents` (scope `returns:write`, the
    `Idempotency-Key` header honoured) takes

    `{ intents, expected_updated_at }`: each intent picks a saved fee rule, adds
    a one-off fee or credit with its

    reason, sets a line's amount or decides its tax, waives a line or removes
    one, and the return's fee plan after

    the change is the answer; `POST /v1/returns/{id}/fee-intents/preview`
    answers the same and writes nothing. A save

    made from an older copy of the return is refused 409 `rma_changed`, a
    closed, voided or cancelled return 409

    `fees_locked_*`, a change the fee plan refuses 422 with `meta.offers` (403
    `fee_tax_override_not_permitted` for a

    tax decision without `returns.override_condition`). On `PATCH
    /v1/returns/{id}`, `return_adjustments` is

    deprecated (paired with the stored lines by key and translated into fee
    intents) and answered with a

    `Deprecation: true` header; the update also takes `adjustment_reason`,
    `restocking_fee_reason` and

    `expected_updated_at` (409 `rma_changed`), refuses `adjustment_lines` with
    422 `return_adjustments_use_fee_intents`,

    and its page says that `notes` and `metadata` are stored nowhere today.


    **October 2026: the return cancel page names every refusal it answers.**
    `POST /v1/returns/{id}/cancel` now

    documents its 422: a return a mis-shipment correction created is refused
    `misshipment_correction_open` while the

    wrong item is still held for it and `misshipment_correction_written_off`
    after its hold was written off, with the

    held units, the reopened order line and the three choices as fields (choose
    one with

    `POST /v1/returns/{id}/misshipment-intent` instead). Its 409 now names
    `spawned_order_open` (an exchange or

    advance-replacement order the return created is still open; `order_id`,
    `order_number` and `door` name it and

    what comes first), and says that a return in a status a cancel does not take
    is refused 409 with no `code`. The

    page says which statuses a cancel takes (pending approval, authorized,
    expected) and that it moves no stock; it

    no longer names a `draft` status, which a return does not have.


    **October 2026: a mis-shipment correction's preview no longer uses up its
    Idempotency-Key.** On

    `POST /v1/orders/{id}/correct-misshipment`, a body with `preview: true` is
    answered without reading the

    `Idempotency-Key` header, and its answer is never stored: the correction
    sent next with the same key is made

    (before, the preview's answer was stored under the key, so that correction
    was refused 409

    `idempotency_key_collision` and nothing was corrected), and a preview sent
    again reads the order anew instead of

    replaying the first one. Every other body is keyed as before. The page no
    longer says a repeated call is safe

    without the key: the same correction sent again without an `Idempotency-Key`
    is a further correction while the

    line still has shipped units, and once none is left it is refused 422
    `quantity_exceeds_shipped`, now named there.


    **October 2026: a return a mis-shipment correction created says what
    replaces a void of it.**

    `GET /v1/returns/{id}/misshipment-intent-preview` (scope `returns:read`)
    reads what the return still holds and

    what each of three choices would book; `POST
    /v1/returns/{id}/misshipment-intent` (scope `returns:write`, the

    `Idempotency-Key` header honoured) makes one of them: `no_replacement`
    closes the reopened units short on the order

    and takes back the discount and tax booked when they shipped (the answer's
    `taken_back` and `books`; nothing is

    refunded), `replacement_later` keeps the return open, and `not_coming_back`
    writes the held wrong item off. A choice

    the return's state does not allow is refused 409
    `misshipment_intent_unavailable`, its reason in `hint`.


    **October 2026: a restocking fee the return computes is held to the entity's
    bounds instead of refused.** On

    `POST /v1/returns/{id}/disposition`, `POST
    /v1/returns/{id}/receive-and-dispose` and `POST /v1/returns/{id}/refund`,

    a restocking fee computed from a percentage that falls outside the entity's
    configured minimum or maximum restocking

    fee is charged at that bound, and the return's fee line names the bound it
    was held to; the 422 answers

    `restocking_fee_below_minimum_at_disposition` and
    `restocking_fee_above_maximum_at_disposition` are no longer given.


    **October 2026: the reason Other says why.** `POST /v1/returns` and `POST
    /v1/returns/{id}/items` take

    `reason_words`. A return or a line whose reason is `other` with nothing
    saying why is refused 422

    `reason_words_required` (a `reason` sent as words counts, and so do words
    the return already holds when lines are

    added); words over 2,000 characters are refused 422 `note_too_long`. The
    words are kept as an internal note of the

    return.


    **October 2026: a line refused for where it belongs names its order, a
    return that refunds nothing is not refused

    for a cash refund, and a fee line cannot mark itself as a rule's.** On `POST
    /v1/returns` and

    `POST /v1/returns/{id}/items`, `multi_order_not_enabled` reads "Each return
    covers one order. Make a separate return

    for <the line's order number>.", `channel_orders_mixed` and
    `mixed_return_destinations` name the line's channel and

    where it ships back, and none of the three starts its hint with the line's
    position (`param` and `index` still name

    it). `POST /v1/returns` with `refund_method` `cash` on an `exchange`,
    `advance_replacement` or `warranty_repair`

    return of an order that holds no payment while an invoice of it is open is
    created with `credit_memo` instead of

    the 422 `nothing_paid_credit_the_invoice`. A `return_adjustments` line no
    longer marks itself as a rule's line: on

    `POST /v1/returns` its `_auto_rule`, a `coupon_id` beside a typed `value`
    and the rule values are ignored and it is

    judged as a typed fee (`fee_exceeds_goods`, `fee_reason_required`); on
    `PATCH /v1/returns/{id}` a line keeps them

    only as the return's own stored line, sent back as it stands.


    **October 2026: a line that makes a return span orders meets the create's
    checks, a return on an invoice never paid

    credits the invoice, and a return's drop-ship destination is its lines'.**
    `POST /v1/returns/{id}/items` answers the

    create's refusals of a return across orders when the added lines make the
    return span orders: 422

    `return_location_required` while the return names no return location and its
    lines shipped from more than one

    warehouse, 422 `refund_method_required_multi_order` while its stored refund
    method is `auto`, and 422

    `multi_order_tax_filing_not_ready`; before, such lines were added. `PATCH
    /v1/returns/{id}` refuses `refund_method`

    `auto` on a return whose lines span orders (422
    `refund_method_required_multi_order`). On `POST /v1/returns` and

    `PATCH /v1/returns/{id}`, `refund_method` `cash` on an order that holds no
    payment while an invoice of it is open is

    refused 422 `nothing_paid_credit_the_invoice`, naming the invoice and the
    open balance; `credit_memo` (the credit

    against the open invoice) and `store_credit` are accepted, and a create that
    sends no method where the entity's

    default is `cash` is created with `credit_memo`. A return of a line that
    ships back to the warehouse is no longer

    marked as shipping back to a drop-ship vendor because another line of its
    order was drop-shipped: a return's

    destination is its lines'.


    **October 2026: a return credits a service line instead of returning it,
    carries one reason for every line, and

    works out the tax on the shipping it gives back.** On `POST /v1/returns` and
    `POST /v1/returns/{id}/items`, a line

    of a service (a product whose type is service, which moves no stock) is
    credited with `items[].credit_only: true`

    and no quantity: it takes the units not yet credited, its credit is its net
    and its tax, and nothing is received.

    A service line sent as goods is refused 422 `service_line_not_returnable`,
    `credit_only` on a line whose goods come

    back 422 `credit_only_not_a_service_line`, and a service line with nothing
    left to credit 422

    `service_line_already_credited`; before, a service line was taken as goods
    coming back. A line of an order that a

    mis-shipment correction re-ships is refused 422 `misshipment_expects_units`
    while that correction's return still

    expects the item shipped in its place. `POST /v1/returns` reads
    `reason_code` (and `reason` when it is one of the

    reason codes) as the reason of every line that sends none. `shipping_refund`
    is capped at the shipping the customer

    paid and has not been given back, and its tax is worked out from the order;
    a `shipping_refund_tax` sent is no

    longer stored. The hints of `return_location_required`,
    `refund_method_required_multi_order` and

    `multi_order_tax_filing_not_ready` read as sentences with no field names.


    **October 2026: free text on an inventory adjustment posts from the reason
    its words mean, and every adjustment

    posts to the income statement.** On `POST /v1/inventory/adjustments`, a
    request (or an item) that sends free text

    `reason` and no `reason_code` or `adjustment_reason_id` now posts from the
    entity's adjustment reason its words

    mean: the text names a reason code of the entity exactly, or its words match
    a reason (damage, cycle count,

    write-off, revaluation, found stock), and text that matches none is the
    Other reason. The adjustment posts to the

    account that reason names for the movement's direction, and the answer names
    the reason and the account

    (`adjustment_reason`, `gl_account_source` `free_text`, `posted_to`). A
    reason with no account for the direction is

    refused 422 `adjustment_reason_needs_account` with `param` `reason` (or
    `items[n].reason`), and nothing is written.

    A reason that requires a note takes the text sent in `reason` as the note; a
    request that sends neither text nor a

    note (`notes`, or a multi-item item's `notes` or the root `memo`) is refused
    422 `adjustment_note_required` with

    `param` `notes` (or `items[n].notes`) on both the single-item and the
    multi-item request.

    The account an adjustment posts to is always an income-statement account
    (cost of goods, expense, other income or

    other expense); any other is refused 422
    `adjustment_account_not_income_statement`. Before, text that matched no

    rule posted to the entity's inventory adjustments holding account on the
    balance sheet. The movement's notes and the

    entry's description still carry the text exactly as sent. This replaces the
    deprecation note below: free text no

    longer chooses an account by itself.


    **October 2026: an inventory adjustment names its reason, a key never names
    a document's uploader, and the flag

    refusals are documented.** `POST /v1/inventory/adjustments` accepts
    `reason_code` (or `adjustment_reason_id`) on a

    single-item request, at the root of a multi-item request (the reason of
    every item that sends none of its own) and

    on each item. The adjustment posts to the account the entity's adjustment
    reason names for the movement's direction,

    exactly as the adjustment screen posts it, and the answer names the reason
    and the account (`adjustment_reason`,

    `gl_account_source`, `posted_to`). A code that names none of the entity's
    active reasons is refused 422

    `adjustment_reason_not_found` with `param` and the active codes in
    `allowed_codes`, and nothing is written; a

    turned-off reason is refused 422 `adjustment_reason_inactive`; a key cannot
    choose an account other than the

    reason's (403 `adjustment_account_choice_requires_user`, or 422 with `param`
    `gl_account_id` for an account the

    adjustment picker does not offer) and cannot post above a reason's approval
    limit (403

    `adjustment_over_approval_threshold`). Before, `reason_code` was not read:
    such an adjustment, and a multi-item

    item's `adjustment_reason_id`, posted where the free text sent them.
    Deprecated: choosing the account through the

    free-text `reason`, which picks it by matching words in the text (text that
    matches none posts to the entity's

    inventory adjustments account); free text keeps choosing the account as it
    does today, and no removal date is set. A

    single-item adjustment answers 200 and a multi-item adjustment 201, as they
    always have (the page listed only 201).

    A retry with the same `Idempotency-Key` now answers the first request's
    response even when a reason, a product, a

    permission or the posting window changed in between; before, such a retry
    could answer a refusal for an

    adjustment that had already been applied. A refusal the adjustment answers
    (a turned-off reason, a product that

    holds no stock, the key creator's permission, the posting window) is
    likewise the answer of its key: once the

    cause is fixed, send the request with a new key.


    `POST /v1/documents` and `POST /v1/documents/confirm` never record a person
    from the request body: a document an API

    key creates names no uploader, and its activity rows name no user (before, a
    `user_id` or `uploaded_by` in the body

    naming an active member of the entity was recorded as that member).


    Fifteen operations answer 422 `invalid_boolean_field`, with `param` naming
    the field and nothing written, when a

    flag is not a JSON `true` or `false`, and each page now says so, naming the
    flags and the values it refuses:

    `POST /v1/locations`, `PATCH /v1/locations/{id}`, `POST` and `PATCH
    /v1/payment-terms`,

    `PATCH /v1/gl-accounts/{id}`, `PATCH /v1/accounts/{id}`, `POST
    /v1/accounts/{id}/addresses`,

    `PATCH /v1/accounts/{id}/addresses/{addr_id}`, `PATCH
    /v1/accounts/{id}/contacts/{cnt_id}`, `PATCH /v1/kits/{id}`,

    `POST` and `PATCH /v1/pricing-policies`, `POST /v1/products` (a `pricing` or
    `purchase_policies` entry),

    `PATCH /v1/products/{id}` and `POST /v1/products/{id}/pricing`. The text
    "true" or "false" and 1 are refused on

    all fifteen (on `PATCH /v1/gl-accounts/{id}` when the body carries no
    `opening_balance`), and 0 and null on all

    but `POST /v1/accounts/{id}/addresses`, which reads 0 and null as `false`
    and creates the address.

    `PATCH /v1/gl-accounts/{id}` reads its flag only when the body carries no
    `opening_balance`: a body that carries

    one posts the account's opening balance and reads no other field. The
    request bodies of

    `PATCH /v1/locations/{id}` and `POST /v1/payment-terms`, and the `pricing`
    entries of `POST /v1/products` and

    `POST /v1/products/{id}/pricing`, now list those flags as booleans. `POST
    /v1/locations` lists the fields it reads

    (`code` is required, the address is one `address` object, and `type`,
    `description`, `sort_order` and the four

    flags are listed); `POST /v1/payment-terms` reads `days` and
    `discount_percent`, not `days_due` and

    `discount_pct`, and only `name` is required; a listed field the operation
    does not read now says so.


    **October 2026: the return create and update contract says what the API
    does.** On `POST /v1/returns`,

    `original_order_id` is derived from the lines when it is omitted, and every
    line names the sales order line it

    comes back from (`items[].order_item_id`; a line without one is refused 422
    `order_line_required`, and that

    refusal is already live). Deprecated now, and answered on a 201, 400, 404 or
    422 answer with a `Deprecation: true`

    response header and a `Link` to this changelog (plus a `Sunset` header once
    the removal date is set):

    `auto_refund: true` and `refund_payment_id` (no longer honored: a return
    never refunds on its own; issue the

    refund with `POST /v1/returns/{id}/refund`; at the removal release
    `auto_refund: true` is refused 422

    `auto_refund_removed`; `auto_refund: false` asks for nothing and is not a
    deprecated use, and the field has no

    default), and the standalone return, a line that sends `product_id` and no
    `order_item_id` (already refused; its

    description leaves the spec at the removal release). `refund_method` lists
    the values the API accepts (`auto`,

    `cash`, `credit_memo`, `store_credit`); `original` and `check`, listed
    before, are refused 400

    `invalid_refund_method`. A return line's `condition` lists the values a line
    stores (`new`, `used`, `damaged`,

    `defective`); `like_new`, `good` and `fair` were never sent and leave the
    `ReturnItem` schema one release later.

    Each return operation's page now names, under the status it is answered
    with, the refusals with their own `code`

    that a request sending the fields the page documents can meet, read from the
    operation's code at every call

    depth and checked by running it: the operation's own checks (a location or a
    vendor the request names that is

    not this entity's among them), the steps it runs (the order line, the
    replacement order line, the carrier label,

    the card refund, the vendor-return line) and the ledger and stock refusals
    it can meet through the return's

    state or the entity's settings. Not repeated on every page: the refusals
    every operation shares (the API key,

    its scopes, its creator's permissions, its network rules, the rate limit and
    the `Idempotency-Key` header, whose

    two answers that header's description names); the answers the database gives
    a value its column does not take,

    on any field of the path, the query or the body: 400 `invalid_id_format`
    (not a valid id), 400

    `invalid_enum_value` (not a value of the field's type, text where a number
    belongs among them), 422 `value_too_long`

    (a text longer than the field holds), 422 `value_out_of_range` (a number
    larger than the field holds), 400

    `not_null_violation`, 400 `check_violation`, 400 `constraint_violated`, 409
    `fk_violation` and 409

    `uniq_violation` (a value a rule of the database refuses), and 409
    `serialization_failure` or 409 `deadlock` (a

    parallel update; a retry clears it), which a page also names where its own
    fields were measured to draw one; and

    an integrity refusal, which reports that records the operation reads
    disagree with each other and is answered

    with its own `code`. The two read operations now document their 400 answer,
    and the add-lines, exchange, advance

    replacement and vendor return operations the 201 they answer (their pages
    said 200). The receive-and-dispose

    lines, the exchange body and the advance replacement lines now list the
    fields of the contract those operations

    read for a caller (receive-and-dispose also reads
    `items[].serial_number_returned`, which is not yet listed); the

    disposition line lists `vendor_id`, and its `condition` lists the four
    values a return line stores (`like_new`,

    `good` and `fair` are still accepted there on a line that sends no `leaves`,
    where they never block a restock, and

    leave the list one release later; a kit line that sends `leaves` refuses
    them 400 `invalid_enum_value`); a

    documented field an operation does not read now says so.
    `estimated_refund_amount`, `money_suppression` and

    `is_pre_settlement` say what the API already did: the estimate is present
    while neither the lines nor the header

    hold a credit (the schema said "while no line holds a credit"). A bulk
    import with `conflict_mode: replace` on

    returns now answers 409 `return_replace_not_supported` naming the existing
    return and writes nothing

    (`MigrationConflictMode` says so); before, it removed the import record from
    the existing return and created a

    second return for the same goods. The webhook endpoint's event list counts
    153 events in 15 families. The

    correction documents, the refile, the returns of an order, the receive
    order, the unexpected arrival and the

    staff RA PDF stay staff-only for now: they have no api-v1 endpoint in this
    release.


    Returns: override_window now needs override_window_reason when a line is
    outside its return window; returns can

    span several orders of one customer where Settings > Returns allows it. On
    `POST /v1/returns`, `return_location_id`

    names the warehouse the goods come back to (needed when the lines span
    orders shipped from more than one

    warehouse), a return across orders names its `refund_method` (`cash`,
    `credit_memo` or `store_credit`), and a sent

    `original_order_id` must be the order of one of the lines; `POST
    /v1/returns/{id}/items` reads `override_window`

    and `override_window_reason` for a line of an order new to the return. The
    pages name the refusals:

    `original_order_not_among_lines`, `return_location_required`,
    `return_location_not_a_warehouse`,

    `refund_method_required_multi_order`, `multi_order_tax_filing_not_ready`,
    `override_window_reason_required`,

    `approval_required_for_added_lines`, and 404 `location_not_found` for a
    `return_location_id` that is not one of

    the entity's locations.


    **October 2026: credit memos in search, and the cross-resource search
    answers by scope.**

    `GET /v1/search/{object}` accepts `credit_memos`: customer credit memos by
    memo number or customer

    name, the exact memo number first, each result naming the memo's status, its
    customer and its return

    when it has one; it requires the `ar:read` scope in addition to
    `search:read` (a key without it is

    refused with 403 `insufficient_scope`), and its cursor (`starting_after`)
    continues exactly where the

    previous page ended. `GET /v1/search` now returns matches: each of its five
    buckets holds results when

    the key also holds that resource's read scope (`orders:read` for orders and
    invoices, `products:read`

    for products, `accounts:read` for accounts, `inventory:read` for serial
    numbers) and stays empty

    otherwise; until now every bucket was empty for every key.
    `meta.did_you_mean` now carries the search

    term when the results are close matches rather than exact ones. No field is
    renamed.


    **October 2026: the order status values are the ones the API sends.** The
    `Order` schema's

    `order_status`, `payment_status`, `fulfillment_status` and `document_type`
    now list the values

    the order endpoints actually return (`order_status`: draft, open,
    processing, fulfilled,

    cancelled, archived, expired, awaiting_ach_clearance; `payment_status`:
    unpaid, partial,

    processing, paid, refunded, terms, overdue, overpaid, partially_refunded;
    `fulfillment_status`:

    unfulfilled, partial, fulfilled, backordered, awaiting_pickup;
    `document_type` adds

    late_fee_invoice). The values themselves did not change; the schema had
    listed names the API

    never sends (`confirmed`, `partially_paid`, `partially_fulfilled`, `voided`,
    `on_hold`), so a

    client generated from it could reject a real order. On `POST /v1/orders`,
    `order_status` is

    limited to `draft` only for a `sales_order` or `invoice`. The refusals of
    the order, refund,

    settings and deposit endpoints are now listed with their `code` values on
    each response.


    **October 2026: a pair of bank lines in transit at a month end is an open
    item there.** `GET /v1/entities/{entity_id}/reconciliation/account-audit`,
    `answers.books_against_bank`,

    each `month_ends` row: when the two bank lines of a pair matched together to
    one bill payment (a

    wire and its return) fall on either side of that date,
    `bank_unbooked_amount` and

    `bank_unbooked_count` now include the line already in the bank, and
    `gl_uncleared_amount` and

    `gl_uncleared_count` include the payment's bank leg;
    `explained_by_open_items` therefore explains

    that date instead of leaving the part in transit unexplained. No field is
    renamed and no other

    date moves; once both lines are in the bank the pair counts nowhere, as
    every other match.


    **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:
  /returns/{id}/receipts/{receipt_id}/unreceive:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: receipt_id
        in: path
        required: true
        description: >-
          A receipt of the return: the `receipt.receipt_id` that `POST
          /returns/{id}/receive` answered.
        schema:
          type: string
          format: uuid
    post:
      tags:
        - Returns
      summary: Un-receive a receipt
      description: >-
        Takes units of one receipt back out of Holding, as if they never
        arrived: the receipt items and quantities in `items`, or, without
        `items`, every unit of the receipt that is not put away. Units already
        put away are refused (422 `units_already_decided`: undo that put-away
        first), and so is a return that settled money until its settlement is
        re-opened (409 `return_money_settled`). A serial-tracked item names the
        serials leaving. When nothing on the return stays received it goes back
        to `authorized`; a receipt that recorded value on receipt has that value
        taken back (`unreceive.journal_entry_ids`). `reason` is kept as a note
        on the return, and the act is written on its activity under the key's
        creator. A request with a `request_key` (else `client_request_id`, else
        the `Idempotency-Key` header) is one act: a retry answers the first act
        and moves nothing. Preview it with `GET
        /returns/{id}/receipts/{receipt_id}/unreceive-preview`. Requires the
        `returns:write` scope and, for the key's creator, the
        `returns.unreceive` permission (`returns.receive` is enough when every
        unit taken is still in Holding on the return's own stock).
      operationId: unreceiveReturnReceiptV1
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - reason
              properties:
                reason:
                  type: string
                  minLength: 10
                  description: >-
                    Why the units are taken back, kept as a note on the return.
                    A missing or empty reason is refused 400 `reason_required`;
                    one under 10 characters 422 `reason_required`.
                request_key:
                  type: string
                  maxLength: 64
                  description: >-
                    The act's own key. A retry with the same key and the same
                    request answers the first act and moves nothing
                    (`unreceive.replayed: true`); the same key with another
                    request is refused 409 `request_key_reused`. More than 64
                    characters is refused 422 `request_key_invalid`.
                client_request_id:
                  type: string
                  maxLength: 64
                  description: Read as `request_key` when `request_key` is not sent.
                items:
                  type: array
                  description: >-
                    The receipt items to take back and how many units of each.
                    Without it, every unit of the receipt that is not put away.
                  items:
                    type: object
                    required:
                      - return_receipt_item_id
                      - quantity
                    properties:
                      return_receipt_item_id:
                        type: string
                        format: uuid
                        description: >-
                          A receipt item of this receipt
                          (`receipt.items[].return_receipt_item_id` of the
                          receive's answer). One that is not a valid id is
                          refused 422 `missing_field`; one listed twice 422
                          `duplicate_receipt_item`.
                      quantity:
                        type: integer
                        minimum: 1
                        description: Whole units, 1 or more (else 422 `quantity_invalid`).
                      serial_numbers:
                        type: array
                        items:
                          type: string
                        description: >-
                          The serials leaving, one per unit, each one in custody
                          on this receipt item. Required when the item holds
                          serials and the units taken are not one for each of
                          them (422 `serials_required`).
      responses:
        '200':
          description: >-
            Un-received. The return as `GET /returns/{id}` answers it, with what
            this act took back as `unreceive` (a retry with the same key answers
            the first act's, `replayed: true`).
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    allOf:
                      - $ref: '#/components/schemas/Return'
                      - type: object
                        required:
                          - unreceive
                        properties:
                          unreceive:
                            $ref: '#/components/schemas/ReturnUnreceiveResult'
        '400':
          description: >
            The request is malformed. `code` includes:

            - `invalid_id_format`: the return id or the receipt id in the path
            is not a valid id (`param` names it).

            - `reason_required`: no reason is sent (`param: reason`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          description: >
            The key may not do this; nothing is un-received. `code` includes:

            - `insufficient_scope`: the key does not carry `returns:write`.

            - `permission_denied`: the key's creator holds neither
            `returns.unreceive` nor, for units still in Holding
              on the return's own stock, `returns.receive` (`permission_key: returns.unreceive`); a key with no creator
              is refused too.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReturnKeyPermissionRefusal'
        '404':
          description: >
            Not found. `code` includes:

            - `return_not_found`: no return with this id exists in this entity.

            - `return_receipt_not_found`: the receipt is not a receipt of this
            return.

            - `return_receipt_item_not_found`: an
            `items[].return_receipt_item_id` is not a receipt item of this
            return.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReturnGoodsRefusal'
        '409':
          description: >
            The return's state refuses the un-receive; nothing is un-received.
            `code` includes:

            - `return_money_settled`: the return has settled money (a credit
            memo or a refund); re-open its settlement
              first, then un-receive.
            - `return_order_set_changed`: the orders the return touches changed
            while it was being locked; nothing was
              written. Send the request again.
            - `request_key_reused`: the key already took back a different set of
            units on this return; a new
              un-receive needs a new key (`param: request_key`).
            - `unreceive_conflict_retry`: another change to the same stock was
            saved at the same moment; nothing was
              un-received. Send the request again.
            - `goods_ledger_needs_repair`: the return's recorded goods do not
            tie for a line it would take back
              (`meta` names the line and how to repair it).
            - `holding_pool_has_unclaimed_units`: the product's Holding stock
            holds units no return accounts for, so
              the return's own units cannot be told apart; count Holding for the product first (`meta.unclaimed`).
            - `misshipment_unit_allocated`: a mis-shipped unit released where it
            was staged is now reserved for an
              order; release that reservation first.
            - `receipt_recorded_without_stock`: the receipt item was recorded as
            received without stock entering;
              resolve it first with `POST /returns/{id}/receipt-items/{receipt_item_id}/repair-stock`.
            - `units_reserved_in_holding`: units in Holding are reserved for an
            order, so they cannot be taken out;
              release the reservation first.
            - `unreceive_layer_conflict`: the product's stock changed while it
            was being taken back; refresh the
              return and try again.
            - `rma_movement_not_written`: the stock movement this step pairs
            with matched a movement the return
              already posted, so nothing moved; refresh the return and check where the units are before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReturnGoodsRefusal'
        '422':
          description: >
            The units cannot be un-received as sent; nothing is un-received.
            `code` includes:

            - `not_in_received_state`: the return is not received or inspecting
            (the hint names its status).

            - `disposition_present`: a line of the receipt has units put away
            that no receipt item records
              (`door: undo_put_away`); undo the put-away first, then un-receive.
            - `nothing_to_unreceive`: the receipt has no unit left to take back.

            - `reason_required`: the reason is under 10 characters (`param:
            reason`).

            - `request_key_invalid`: `request_key` is longer than 64 characters.

            - `receipt_item_not_on_receipt`: an `items[]` entry is a receipt
            item of another receipt of this return.

            - `missing_field`: an `items[].return_receipt_item_id` is missing or
            not a valid id (`param` names it).

            - `duplicate_receipt_item`: a receipt item is listed twice.

            - `quantity_invalid`: an `items[].quantity` is not a whole number of
            units, 1 or more.

            - `units_already_decided`: some of the units asked for are put away
            (`meta.decided`, `door: undo_put_away`
              in `meta`); undo that put-away first.
            - `unreceive_exceeds_received`: more units are asked for than the
            receipt item holds received.

            - `units_not_in_holding`: units are no longer in Holding
            (`meta.removed_by` names the movements that took
              them); record them as missing once a count confirms it.
            - `serials_required`: the receipt item holds serials in custody and
            the units taken are not one for each
              of them; name the serials leaving in `items[].serial_numbers`.
            - `serial_count_mismatch`: `items[].serial_numbers` does not name
            one serial per unit.

            - `serial_not_in_custody`: a serial named is not in custody on that
            receipt item.

            - `serial_numbers_invalid` / `serial_repeated`:
            `items[].serial_numbers` is not an array of non-empty
              strings, or names one serial twice.
            - `receipt_valuation_accounts_not_configured`: the receipt recorded
            value on receipt and the Inventory or
              Cost of Goods Sold account is not mapped, so the value cannot be taken back.
            - `period_closed`: the accounting period of today, the date the
            entry takes the value back on, is closed.

            - `account_inactive`: an account the entry names is inactive.

            - `account_is_header`: an account the entry names is a header
            account, which takes no posting.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReturnGoodsRefusal'
      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. A second request with the same

        key sent while the first is still running waits for it and returns its

        answer; if the first is still running after 20 seconds the second

        answers 409 `idempotency_key_in_use` (retryable, nothing done). An

        answer carrying `retryable: true` is not cached. Reusing a key with a

        different request body answers 409 `idempotency_key_collision` (nothing

        done; send the new request with a fresh key). The replay, the wait and

        these two 409 codes come from the idempotency layer, so they apply to

        the operations that run it, each of which can answer the two codes

        whether or not its own 409 response lists them. The handlers of these

        operations take the header but do not run the layer, so on them the key

        neither replays nor answers either code: createPaymentPlan,

        chargePaymentPlanInstallment, setProductLocations, updateOrder,

        emailOrder, updateOrderItem, confirmPickup, markOrderNoShow,

        applyExistingPaymentToInvoices, updatePackage, movePackageItem,

        changePackageBox, changePackageCarrier, voidPackageLabel,

        printPackageLabel, emailPackage, setProNumberV1, setPackageProNumber,

        sendPurchaseOrderToVendorV1, notifyRevisionPurchaseOrderV1,

        addMaintenanceLog, generateBOLV1,

        createReportJobV1 and customerAcknowledgeRevision;
        recordVendorPrepaymentV1

        and recordCustomerDepositV1 hand the key to their own write instead.
      schema:
        type: string
        maxLength: 255
  schemas:
    Return:
      type: object
      description: >-
        RMA (return merchandise authorization). Phase A -- refund_amount is
        readOnly via PATCH; written only by POST /v1/returns/{id}/refund.
      properties:
        id:
          type: string
          format: uuid
        object:
          type: string
          enum:
            - return
        entity_id:
          type: string
          format: uuid
          readOnly: true
        return_number:
          type: string
          readOnly: true
        status:
          type: string
          enum:
            - authorized
            - expected
            - received
            - inspecting
            - restocked
            - written_off
            - closed
            - sent_to_vendor
            - cancelled
          readOnly: true
          description: >-
            Use action endpoints to transition status; PATCH cannot write
            status.
        original_order_id:
          type: string
          format: uuid
          nullable: true
          description: >-
            The order the return comes back from; null when the return names no
            order.
        account_id:
          type: string
          format: uuid
        return_location_id:
          type: string
          format: uuid
          nullable: true
          readOnly: true
          description: >-
            The warehouse the goods come back to, when one is set: on `POST
            /returns` (the `return_location_id` the request sends, else its
            order's warehouse when the order has one) or by `POST
            /returns/{id}/return-location`; null when none is. `GET /returns`
            with a location scopes the return by it, else by its originating
            order's location.
        return_location_source:
          type: string
          nullable: true
          readOnly: true
          enum:
            - order
            - manual
            - receipt
            - backfill_order
            - backfill_receipt
          description: >-
            Where `return_location_id` came from: `order` when the create took
            its order's warehouse, `manual` when the create or `POST
            /returns/{id}/return-location` named it; `receipt`, `backfill_order`
            and `backfill_receipt` are the other values the field may hold. Null
            exactly when `return_location_id` is.
        vendor_id:
          type: string
          format: uuid
          nullable: true
        vendor_rma_number:
          type: string
          nullable: true
        inspection_required:
          type: boolean
        restocking_fee:
          type: number
          nullable: true
        restocking_bound:
          $ref: '#/components/schemas/ReturnRestockingBound'
        refund_amount:
          type: number
          nullable: true
          readOnly: true
          description: >-
            SSOT: written only by POST /v1/returns/{id}/refund (refundReturn
            canonical). PATCH cannot write this field.
        return_to_vendor:
          type: boolean
        refund_hold:
          type: boolean
          nullable: true
          readOnly: true
          description: >-
            The refund hold. null = never held; true = accounting holds the
            refund with a question for the office, or a vendor claim of the
            return holds it until the vendor answers, and POST
            /v1/returns/{id}/refund answers 409 `refund_on_hold` until the hold
            is released; false = the hold is released (the office answered, or
            the vendor's answer released it). Set and released in the Arcus app
            only; nothing is refunded and no amount owed changes while it is
            held.
        refund_hold_note_id:
          type: string
          format: uuid
          nullable: true
          readOnly: true
          description: >-
            The note that holds the hold's question; kept after the hold is
            released.
        refund_hold_by:
          type: string
          format: uuid
          nullable: true
          readOnly: true
          description: Who put the refund on hold; kept after release.
        refund_hold_at:
          type: string
          format: date-time
          nullable: true
          readOnly: true
          description: When the refund was put on hold; kept after release.
        is_pre_settlement:
          type: boolean
          readOnly: true
          description: >-
            true while the return has not settled yet: neither its lines nor its
            header carry a credit and it is not closed. The estimated_* fields
            then say what the return is expected to credit. A closed return is
            never pre-settlement, including one settled at 0.00 by its outcome.
        estimated_refund_amount:
          type: number
          readOnly: true
          description: >-
            Goods the return is expected to credit, present while neither the
            lines nor the header hold a credit: while is_pre_settlement is true,
            and on a closed return whose settlement credited nothing, where it
            is what a refund would still pay. On an imported return whose lines
            hold no credit, its header figure. Absent once a line holds a
            credit.
        estimated_refund_tax:
          type: number
          readOnly: true
          description: >-
            Tax the return is expected to credit, present on the same returns as
            estimated_refund_amount. Absent once a line holds a credit.
        money_suppression:
          type: object
          readOnly: true
          description: >-
            Present while neither the lines nor the header hold a credit
            (is_pre_settlement is true, or a closed return whose settlement
            credited nothing): how the recorded outcome of the return shapes the
            estimate. A Replacement or No refund outcome credits nothing for its
            units; when this cannot be read, nothing is estimated (unreadable is
            true).
          properties:
            basis:
              type: string
              nullable: true
              enum:
                - decision
                - legacy_even_swap
                - none
              description: >-
                decision: an outcome is recorded on a line; legacy_even_swap: a
                free replacement or exchange is linked and no outcome is
                recorded; none: neither.
            whole_no_money:
              type: boolean
              description: true when every unit of the return is decided to credit nothing.
            unreadable:
              type: boolean
              description: true when the outcome could not be read; no money is estimated.
            waived_goods:
              type: number
              description: Goods not credited because of the recorded outcome.
            waived_tax:
              type: number
              description: Tax not credited because of the recorded outcome.
            legacy_swap:
              type: object
              nullable: true
              description: >-
                The linked replacement or exchange order when basis is
                legacy_even_swap.
        source:
          type: string
          enum:
            - csr
            - customer_portal
            - marketplace
          nullable: true
        notes:
          type: string
          nullable: true
        metadata:
          type: object
          nullable: true
        received_at:
          type: string
          format: date-time
          nullable: true
          readOnly: true
        closed_at:
          type: string
          format: date-time
          nullable: true
          readOnly: true
        cancelled_at:
          type: string
          format: date-time
          nullable: true
          readOnly: true
        return_label_refund_status:
          type: string
          nullable: true
          readOnly: true
          enum:
            - pending
            - refunded
            - denied
            - invalid
            - error
          description: >-
            The carrier's answer to the return label's refund request (pending,
            refunded, denied, invalid, error); null while none was asked for or
            none is answered yet. A cancel or a void of the return asks it for
            an unused label.
        return_label_void_requested_at:
          type: string
          format: date-time
          nullable: true
          readOnly: true
          description: >-
            When the return label's refund was asked for (the cancel or the void
            of its return).
        return_label_voided_at:
          type: string
          format: date-time
          nullable: true
          readOnly: true
          description: >-
            When the carrier accepted the return label's refund request (pending
            or refunded): the label is void and can no longer be used.
        return_label_refunded_at:
          type: string
          format: date-time
          nullable: true
          readOnly: true
          description: When the carrier confirmed the return label's refund.
        return_label_refund_detail:
          type: string
          nullable: true
          readOnly: true
          maxLength: 300
          description: >-
            The carrier's own words when it did not take the return label's
            refund request (the label is then still live).
        return_label_freight_je_id:
          type: string
          format: uuid
          nullable: true
          readOnly: true
          description: >-
            The return label's original freight entry (DR freight out / CR
            carrier clearing), stamped when its freight is reversed.
        return_label_freight_owed_at:
          type: string
          format: date-time
          nullable: true
          readOnly: true
          description: >-
            Set while the return label's freight reversal did not go through;
            cleared when it does.
        return_label_freight_error:
          type: string
          nullable: true
          readOnly: true
          maxLength: 500
          description: >-
            Why the return label's freight reversal did not go through, in
            words.
        created_at:
          type: string
          format: date-time
          readOnly: true
        updated_at:
          type: string
          format: date-time
          readOnly: true
    ReturnUnreceiveResult:
      type: object
      description: What an un-receive took back.
      required:
        - replayed
        - note_id
        - items
        - total_unreceived
        - journal_entry_ids
        - rollup
        - line_credit
        - status
      properties:
        replayed:
          type: boolean
          description: >-
            True when this answers an earlier act with the same `request_key`;
            nothing moved.
        note_id:
          type: string
          format: uuid
          description: The note on the return that keeps the reason.
        items:
          type: array
          items:
            $ref: '#/components/schemas/ReturnUnreceivedItem'
        total_unreceived:
          type: integer
        journal_entry_ids:
          type: array
          items:
            type: string
            format: uuid
          description: >-
            The entries that took back the value of a receipt that recorded
            value on receipt.
        rollup:
          type: array
          nullable: true
          description: The return's lines as the act left them (null on a retry).
          items:
            type: object
            additionalProperties: true
        line_credit:
          type: object
          nullable: true
          additionalProperties: true
          description: >-
            Null unless a line the act emptied still carried a refund figure:
            then `cleared: true` with each line's figures before and after (and
            what the return was owed before and after), or `cleared: false` with
            the document that carries the credit (`carried_by`).
          required:
            - cleared
            - lines
          properties:
            cleared:
              type: boolean
            lines:
              type: array
              items:
                type: object
                additionalProperties: true
        status:
          type: string
          description: The return's status after the act.
    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
    ReturnKeyPermissionRefusal:
      description: >-
        A return step refused for the permission of the key's creator: the
        canonical envelope and the permission refused. Nothing is written.
      allOf:
        - $ref: '#/components/schemas/ErrorEnvelope'
        - type: object
          properties:
            message:
              type: string
              description: >-
                On `permission_denied` for a key with a creator: the permission
                refused, in words.
            permission_key:
              type: string
              description: 'On `permission_denied`: the permission refused.'
            role:
              type: string
              nullable: true
              description: >-
                On `permission_denied` for a key with a creator: the creator's
                role in this entity (null for a custom role).
    ReturnGoodsRefusal:
      description: >-
        A goods step's refusal: the canonical envelope, the action that clears
        it and the facts that say what refused it. Nothing is written.
      allOf:
        - $ref: '#/components/schemas/ErrorEnvelope'
        - type: object
          properties:
            door:
              type: string
              description: >-
                On `disposition_present`: the action that clears it,
                `undo_put_away`.
            return_receipt_item_id:
              type: string
              format: uuid
              description: >-
                On a receipt's un-receive that names an item of another receipt
                of the return: that item.
            meta:
              type: object
              additionalProperties: true
              description: >-
                The facts of the refusal, as the refusal names them: the receipt
                item (`return_receipt_item_id`), the units it concerns (`units`,
                `decided`, `undecided`, `requested`, `received`), the movements
                that took units out of Holding (`removed_by`), the units of the
                product's Holding stock no return accounts for (`unclaimed`),
                the action that clears it (`door`), and the return's status
                (`status`).
    ReturnRestockingBound:
      type: object
      description: >-
        What the entity's restocking fee bounds (Settings > Returns) held of a
        computed restocking fee, and why: the fee is held to the minimum or the
        maximum, never refused. On a return (`GET /returns/{id}`, and each
        operation that answers the return as it reads), it is the bound of the
        return's restocking fee (`restocking_fee`): present while the bounds
        hold that fee on a return that is not closed (or is closed with its
        restocking fee still a percentage to settle); absent when they hold
        nothing, and on a closed return whose fee its close posted. On a fee
        line (`POST /returns/{id}/fee-intents` and its preview), it is present
        on a line the bounds hold. Show `words` as it is; compose no sentence of
        your own from the figures.
      required:
        - code
        - entity_fee_min
        - entity_fee_max
        - outcome
        - from
        - to
        - source
        - words
      properties:
        code:
          type: string
          enum:
            - restocking_fee_above_entity_max
            - restocking_fee_below_entity_min
          description: >-
            Which bound holds the fee: `restocking_fee_above_entity_max`, the
            fee is limited to the maximum; `restocking_fee_below_entity_min`,
            the fee is raised to the minimum.
        entity_fee_min:
          type: number
          nullable: true
          description: The minimum restocking fee, in dollars; null when none is set.
        entity_fee_max:
          type: number
          nullable: true
          description: The maximum restocking fee, in dollars; null when none is set.
        outcome:
          type: string
          enum:
            - clamped
          description: 'What the bound did: the fee is held to it, never refused.'
        from:
          type: number
          description: >-
            The fee the calculation gives on the goods, in dollars, before the
            bound.
        to:
          type: number
          description: 'The fee the bound holds it to, in dollars: the figure charged.'
        source:
          type: string
          enum:
            - stamp
            - live
          description: >-
            Where the bounds were read: `stamp`, the bounds in force when the
            return was authorized; `live`, the current bounds (the return
            carries no record of the bounds it was authorized under).
        words:
          type: string
          description: >-
            The sentence to show, for example "Restocking fee $15.00, limited to
            the entity maximum of $10.00 (Settings > Returns)." A minimum reads
            "raised to the entity minimum of" the figure.
    ReturnUnreceivedItem:
      type: object
      description: One receipt item an un-receive took units from.
      required:
        - return_receipt_item_id
        - unreceived
        - stage_event_id
      properties:
        return_receipt_item_id:
          type: string
          format: uuid
        return_item_id:
          type: string
          format: uuid
        receipt_seq:
          type: integer
        unreceived:
          type: integer
          description: The units this act took back.
        quantity_unreceived:
          type: integer
          description: Every unit of the item un-received so far.
        stock_mode:
          type: string
          description: How the receipt held the units (`holding`, `stage_release`, `none`).
        case:
          type: string
          nullable: true
        targets:
          type: array
          items:
            $ref: '#/components/schemas/ReturnUnreceiveTarget'
        adoption:
          type: object
          nullable: true
          additionalProperties: true
        inventory_transaction_ids:
          type: array
          items:
            type: string
            format: uuid
        value_relieved:
          type: number
          description: The value the act took out of the stock record.
        journal_entry_id:
          type: string
          format: uuid
          nullable: true
        serial_numbers:
          type: array
          items:
            type: string
          description: The serials that left custody.
        order_line:
          type: object
          description: The order line's returned count, lowered by the act.
          required:
            - order_item_id
            - lowered
            - clamped
          properties:
            order_item_id:
              type: string
              format: uuid
              nullable: true
            lowered:
              type: integer
            clamped:
              type: integer
        stage_event_id:
          type: string
          format: uuid
          nullable: true
    ReturnUnreceiveTarget:
      type: object
      description: >-
        One stock source an un-receive takes units out of: a cost batch in
        Holding, or the place a mis-shipped unit is staged again.
      required:
        - location_id
        - quantity
      properties:
        fifo_layer_id:
          type: string
          format: uuid
        location_id:
          type: string
          format: uuid
        quantity:
          type: integer
        origin:
          type: string
          description: >-
            Where the batch came from (`receipt`, `adopted`, and the other
            origins of the stock history).
        adopted:
          type: boolean
        restage:
          type: boolean
          description: True when the unit is staged again where a mis-shipment was staged.
        unit_cost:
          type: number
          nullable: true
        layer_remaining:
          type: number
          description: The units the batch holds.
        value:
          type: number
          description: >-
            On the act's answer: the value the step took out of the stock
            record.
  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.