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

# Change a return's fees

> Changes a return's fee lines one change at a time: pick a saved fee rule, add a one-off fee or credit with its reason, set a line's amount or decide its tax, waive a line, or remove one. Every change runs the return's one fee plan (its rules, caps, waivers and tax), and the plan's lines are what is stored, with one activity row per change naming the key's creator. Send `expected_updated_at`, the return's `updated_at` as you read it: a save made from an older copy is refused 409 `rma_changed` and nothing is written. A refusal the save causes refuses the whole save (nothing is written) and lists every refusal with what can be done about it in `meta.offers`; a refusal that stood before is answered in `refusals` and refuses nothing. A closed, voided or cancelled return takes no fee change (409). Nothing posts and no money moves: the fees post when the return closes. Requires the `returns:write` scope and the `returns.edit` permission for the key's creator (a tax decision also takes `returns.override_condition`). Preview the same save with `POST /returns/{id}/fee-intents/preview`.



## OpenAPI

````yaml /openapi.yaml post /returns/{id}/fee-intents
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}/fee-intents:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
    post:
      tags:
        - Returns
      summary: Change a return's fees
      description: >-
        Changes a return's fee lines one change at a time: pick a saved fee
        rule, add a one-off fee or credit with its reason, set a line's amount
        or decide its tax, waive a line, or remove one. Every change runs the
        return's one fee plan (its rules, caps, waivers and tax), and the plan's
        lines are what is stored, with one activity row per change naming the
        key's creator. Send `expected_updated_at`, the return's `updated_at` as
        you read it: a save made from an older copy is refused 409 `rma_changed`
        and nothing is written. A refusal the save causes refuses the whole save
        (nothing is written) and lists every refusal with what can be done about
        it in `meta.offers`; a refusal that stood before is answered in
        `refusals` and refuses nothing. A closed, voided or cancelled return
        takes no fee change (409). Nothing posts and no money moves: the fees
        post when the return closes. Requires the `returns:write` scope and the
        `returns.edit` permission for the key's creator (a tax decision also
        takes `returns.override_condition`). Preview the same save with `POST
        /returns/{id}/fee-intents/preview`.
      operationId: applyReturnFeeIntentsV1
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReturnFeeIntentsRequest'
      responses:
        '200':
          description: >-
            The changes were saved. The answer is the return's fee plan after
            them, read inside the save.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReturnFeeIntentsResult'
        '400':
          description: |
            The request is malformed. `code` includes:
            - `invalid_id_format`: the path id is not a valid id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          description: >
            The key may not make this change; nothing is written. `code`
            includes:

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

            - `permission_denied`: the key's creator does not hold
            `returns.edit` (`permission_key` names it); a key
              with no creator is refused too.
            - `fee_tax_override_not_permitted`: an `override` decides a fee's
            tax and the key's creator does not hold
              `returns.override_condition` (`meta` names the intent).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReturnFeeIntentRefusal'
        '404':
          description: |
            Not found. `code` includes:
            - `return_not_found`: no return with this id exists in this entity.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '409':
          description: >
            The return changed, or its state takes no fee change; nothing is
            written. `code` includes:

            - `rma_changed`: the return changed since `expected_updated_at`
            (`updated_at` says when it last did).
              Read it again and make the change again.
            - `fees_locked_use_reopen_settlement`: the return is closed and its
            fees are posted; reopen its
              settlement to change a fee (`status` is `closed`).
            - `fees_locked_rma_voided` / `fees_locked_rma_cancelled`: the return
            is voided or cancelled (`status`
              names it); a voided return is un-voided first.
            - `line_not_found`: an intent names a `key` the return's lines do
            not hold (it changed since it was read);
              read it again (`meta` names the intent).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReturnFeeIntentRefusal'
        '422':
          description: >
            The change cannot be made as sent; nothing is written. `param` names
            the intent (`intents[0]`) or the

            field, and a refusal of the fee plan carries `meta.refusals` and
            `meta.offers`. `code` includes:

            - `fee_intents_required`: `intents` is missing or empty.

            - `expected_updated_at_required`: `expected_updated_at` is missing
            or not a date.

            - `intent_invalid`: an intent's `op` is not one of the five, it
            carries a field its `op` does not take, a
              `key` is missing, or the save sends more than 50 intents.
            - `client_field_forbidden`: an intent carries a field only the
            server sets (such as `source`,
              `final_amount`, `waived` or `deferred_pct`).
            - `fee_class_required`: a one-off fee names no fee type.

            - `value_invalid`: `value` is not an amount at or above zero (or a
            percentage above 100), or `mode` is not
              one of `flat`, `percentage`, `label_cost` (`label_cost` is a fee's only).
            - `override_invalid`: an `override` sends neither `amount` nor
            `taxable`, or an amount below zero.

            - `reason_required`: a one-off line has no `reason_code`, or an
            `override`, `waive` or `remove` has no
              `reason`.
            - `note_required`: a one-off line whose `reason_code` is `other` has
            a note under 10 characters.

            - `ad_hoc_fees_not_allowed`: one-off fees are turned off (Settings >
            Returns); pick a saved fee rule.

            - `rule_already_on_plan`: the picked rule is already on the return.

            - `rule_not_found` / `rule_not_return_fee`: the picked rule is not
            one of this entity's, or is not a
              return fee rule.
            - `rule_inactive` / `rule_out_of_window`: the picked rule is
            inactive, or outside the dates it applies on.

            - `rule_scope_mismatch` / `rule_account_mismatch`: the picked rule
            does not reach the return's products, or
              its account.
            - `rule_not_resolved`: the picked rule could not be applied to the
            return.

            - `fee_rule_class_missing` / `fee_rule_calc_unsupported`: the picked
            rule has no fee type, or is
              calculated in a way a return cannot charge.
            - `fee_tax_override_on_exempt_order`: an `override` taxes a fee of a
            tax-exempt order.

            - `fee_tax_override_on_credit_line`: an `override` decides the tax
            of a credit line, which is never taxed.

            - `fee_tax_override_on_restocking_box`: an `override` decides the
            tax of the restocking fee, which the
              entity's fee tax setting decides; set its amount or waive it instead.
            - `credit_line_cannot_be_waived`: a `waive` names a credit line;
            remove it or set its amount.

            - `auto_rule_line_use_waive`: a `remove` names an automatic rule's
            line, which comes back on the next save;
              waive it or set its amount (`meta.offers`).
            - `header_line_use_waive`: a `remove` names the line the return
            builds from its restocking fee or its
              label; waive it or set its amount (`meta.offers`).
            - `auto_rule_no_longer_applies`: a `waive` or an `override` names an
            automatic fee whose rule no longer
              reaches the return, so it charges nothing; remove it (`meta.offers`).
            - `label_line_follows_the_label`: an `override` or `waive` names the
            return label's charge, which follows
              the label and its cost policy.
            - `restocking_fee_beside_restocking_box`: the save would charge a
            restocking fee twice, as a line and as
              the return's restocking fee (`meta.offers`: waive the restocking fee, or remove the line).
            - `handling_line_may_be_rule_line` / `rule_on_two_lines`: the save
            would charge a fee twice: a typed
              handling line beside a handling rule's line, or one rule on two lines (`meta.offers`).
            - `fee_exceeds_goods`: a fee amount a person sets (a one-off fee, or
            a line's amount) is the whole goods
              of the return or more (`fee_amount`, `goods_value`).
            - `fees_exceed_goods`: the fees would be more than the goods the
            return credits (`meta.offers`: change an
              amount, waive a fee, or decide the return as No refund).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReturnFeeIntentRefusal'
        '503':
          description: >
            The fee rules could not be read; nothing is written. Try again.
            `code` includes:

            - `fee_rules_read_incomplete`: the read of the entity's fee rules
            for a `pick_rule` did not complete.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReturnFeeIntentRefusal'
      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:
    ReturnFeeIntentsRequest:
      type: object
      description: >-
        One save: the changes to make, one intent each, and the copy of the
        return they were made from.
      required:
        - intents
        - expected_updated_at
      properties:
        intents:
          type: array
          minItems: 1
          maxItems: 50
          description: >-
            The changes, applied in order; told apart by `op`. A key no intent's
            `op` takes is refused 422 `intent_invalid` (or
            `client_field_forbidden` for a field only the server sets, such as
            `source`, `final_amount` or `waived`).
          items:
            oneOf:
              - $ref: '#/components/schemas/ReturnFeeIntentPickRule'
              - $ref: '#/components/schemas/ReturnFeeIntentAddAdHoc'
              - $ref: '#/components/schemas/ReturnFeeIntentOverride'
              - $ref: '#/components/schemas/ReturnFeeIntentWaive'
              - $ref: '#/components/schemas/ReturnFeeIntentRemove'
        expected_updated_at:
          type: string
          format: date-time
          description: >-
            The return's `updated_at` as you read it (on `GET /returns/{id}`, or
            the `updated_at` of the last fee change's answer). A save made from
            an older copy is refused 409 `rma_changed`; nothing is written.
    ReturnFeeIntentsResult:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - return_id
            - return_number
            - preview
            - updated_at
            - lines
            - totals
            - restocking_fee
            - refusals
            - offers
            - notices
            - allow_ad_hoc_fees
            - books
          properties:
            return_id:
              type: string
              format: uuid
            return_number:
              type: string
            preview:
              type: boolean
              description: True on the preview (nothing was written).
            updated_at:
              type: string
              format: date-time
              description: >-
                The return's `updated_at` after the save (the preview: as read).
                Send it as `expected_updated_at` with the next change.
            lines:
              type: array
              items:
                $ref: '#/components/schemas/ReturnFeePlanLine'
            totals:
              type: object
              required:
                - fees
                - credits
                - net_fee_income
                - uncapped_fees
                - taxable_fees
                - fee_tax
                - by_class
              properties:
                fees:
                  type: number
                  description: The fees the lines charge.
                credits:
                  type: number
                  description: The credits the lines give.
                net_fee_income:
                  type: number
                  description: '`fees` less `credits`.'
                uncapped_fees:
                  type: number
                  description: The fees before the cap (the fees never exceed the goods).
                taxable_fees:
                  type: number
                  description: The fees sent for tax.
                fee_tax:
                  type: number
                  description: >-
                    Always 0 here: the tax on the fees is computed when the
                    refund is filed.
                by_class:
                  type: object
                  additionalProperties:
                    type: number
                  description: The fees by fee type.
            restocking_fee:
              type: number
              description: The return's restocking fee after the change.
            refusals:
              type: array
              items:
                $ref: '#/components/schemas/ReturnFeePlanRefusal'
              description: >-
                Refusals that stood before this save and still stand (a save is
                refused only for one it causes). A step that commits the return
                (Decide, the close) refuses while one stands.
            offers:
              type: array
              items:
                $ref: '#/components/schemas/ReturnFeeOffer'
              description: What can be done about each of `refusals`.
            notices:
              type: array
              items:
                $ref: '#/components/schemas/ReturnFeePlanNotice'
            allow_ad_hoc_fees:
              type: boolean
              description: Whether one-off fees are allowed (Settings > Returns).
            books:
              type: object
              required:
                - lines
                - books_nothing
              description: What this change books, in words.
              properties:
                lines:
                  type: array
                  items:
                    type: object
                    required:
                      - kind
                      - text
                    properties:
                      kind:
                        type: string
                        enum:
                          - note
                          - books_nothing
                      text:
                        type: string
                      amount:
                        type: number
                        description: 'On the totals sentence: the fees less the credits.'
                books_nothing:
                  type: boolean
                  description: >-
                    Always true: a fee change posts no journal entry and moves
                    no money; the fees post when the return closes.
            logged:
              type: integer
              description: >-
                The activity rows the save wrote (one per change, and one per
                new notice). Absent on the preview.
    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
    ReturnFeeIntentRefusal:
      description: >-
        The refusal of a fee change: the shared error envelope, the request part
        at fault and, for a refusal of the fee plan, every refusal with what can
        be done about it. `hint` is the sentence to show. Nothing is written.
      allOf:
        - $ref: '#/components/schemas/ErrorEnvelope'
        - type: object
          properties:
            meta:
              type: object
              description: >-
                On a refusal of the fee plan (and
                `auto_rule_no_longer_applies`): every refusal the save caused,
                and the offers.
              required:
                - refusals
                - offers
              properties:
                refusals:
                  type: array
                  items:
                    $ref: '#/components/schemas/ReturnFeePlanRefusal'
                offers:
                  type: array
                  items:
                    $ref: '#/components/schemas/ReturnFeeOffer'
            fee_amount:
              type: number
              description: 'On `fee_exceeds_goods`: the fee asked for.'
            goods_value:
              type: number
              description: 'On `fee_exceeds_goods`: the goods the return credits.'
            updated_at:
              type: string
              format: date-time
              description: 'On `rma_changed`: the return''s `updated_at` now.'
            status:
              type: string
              description: 'On `fees_locked_*`: the return''s status.'
            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 (`returns.edit`).'
            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).
    ReturnFeeIntentPickRule:
      type: object
      description: >-
        Adds a saved return fee rule to the return (Settings > Adjustments &
        Coupons); the rule's own calculation, caps, tax and waiver apply.
      required:
        - op
        - coupon_id
      properties:
        op:
          type: string
          enum:
            - pick_rule
        coupon_id:
          type: string
          format: uuid
          description: The return fee rule to add.
    ReturnFeeIntentAddAdHoc:
      type: object
      description: >-
        Adds a one-off fee (or credit) of a fee type, with its reason. Refused
        422 `ad_hoc_fees_not_allowed` while Settings > Returns turns one-off
        fees off.
      required:
        - op
        - value
        - reason_code
      properties:
        op:
          type: string
          enum:
            - add_ad_hoc
        direction:
          type: string
          enum:
            - fee
            - credit
          default: fee
        fee_class:
          type: string
          enum:
            - restocking
            - handling
            - return_shipping
            - processing
            - other
          description: Required for a fee; a credit takes none.
        label:
          type: string
          description: >-
            The line's words (the first 80 characters are kept); the fee type's
            own label when absent.
        mode:
          type: string
          enum:
            - flat
            - percentage
            - label_cost
          default: flat
          description: >-
            `flat`: `value` is dollars; `percentage`: a percentage of the goods
            the return credits; `label_cost`: a percentage of the return label's
            cost (a fee only).
        value:
          type: number
          minimum: 0
          description: Dollars for `flat`; from 0 to 100 for `percentage` and `label_cost`.
        reason_code:
          type: string
          enum:
            - original_shipping_recovery
            - return_shipping
            - repackaging
            - missing_parts
            - damage_found
            - late_return
            - goodwill
            - other
        note:
          type: string
          description: Required (at least 10 characters) when `reason_code` is `other`.
    ReturnFeeIntentOverride:
      type: object
      description: >-
        Sets one line's amount, or decides its tax, or both; each with a reason.
        A tax decision takes the `returns.override_condition` permission for the
        key's creator (403 `fee_tax_override_not_permitted` otherwise); a credit
        line and the restocking fee take none.
      required:
        - op
        - key
        - reason
      properties:
        op:
          type: string
          enum:
            - override
        key:
          type: string
          description: The line's `key`, as `lines[]` answers it.
        amount:
          type: number
          minimum: 0
          nullable: true
          description: The line's amount in dollars. Send `amount`, `taxable` or both.
        taxable:
          type: boolean
          description: Whether the fee is taxed.
        reason:
          type: string
          minLength: 1
    ReturnFeeIntentWaive:
      type: object
      description: >-
        Waives one fee line (its amount becomes 0.00) with a reason. A credit
        line cannot be waived.
      required:
        - op
        - key
        - reason
      properties:
        op:
          type: string
          enum:
            - waive
        key:
          type: string
        reason:
          type: string
          minLength: 1
    ReturnFeeIntentRemove:
      type: object
      description: >-
        Removes one line with a reason. An automatic rule's line and a line the
        return builds from its own restocking fee or label come back on every
        save: waive them instead (422 `auto_rule_line_use_waive`,
        `header_line_use_waive`).
      required:
        - op
        - key
        - reason
      properties:
        op:
          type: string
          enum:
            - remove
        key:
          type: string
        reason:
          type: string
          minLength: 1
    ReturnFeePlanLine:
      type: object
      description: >-
        One fee or credit line of the return as the fee plan reads it after the
        changes. `final_amount` is what the line charges (a fee) or credits (a
        credit) on the return's goods today; the amounts post when the return
        closes. A line the return already stored also carries the keys its
        stored copy holds for every reader of the stored lines (`fee_type`,
        `mode`, `value`, `calculated_amount`, `gl_account_key`, and on a rule's
        or a waived line its markers); a line this change made carries the
        plan's keys only.
      required:
        - key
        - direction
        - source
        - fee_class
        - label
        - gl_account_id
        - calc
        - computed_amount
        - override
        - final_amount
        - taxable
        - taxable_source
        - waived
        - waiver_source
        - by
        - at
        - disclosed_at
      properties:
        key:
          type: string
          description: >-
            The line's name on this return: what `override`, `waive` and
            `remove` take.
        direction:
          type: string
          enum:
            - fee
            - credit
        source:
          type: object
          description: Where the line comes from.
          required:
            - kind
          properties:
            kind:
              type: string
              enum:
                - rule
                - ad_hoc
                - legacy
              description: >-
                rule: a saved fee rule; ad_hoc: a one-off fee with its reason;
                legacy: a line not made by a saved rule or a one-off fee: typed
                by hand on the return, or built from the return's restocking fee
                or its return label's cost policy (see `from`).
            coupon_id:
              type: string
              format: uuid
              description: 'On a rule''s line: the rule.'
            applied:
              type: string
              enum:
                - auto
                - picked
              description: >-
                On a rule's line: applied by the rule itself, or picked by a
                person.
            snapshot:
              type: object
              additionalProperties: true
              description: >-
                On a rule's line: the rule as it stood when the line took it
                (name, fee type, calculation, caps, tax, waiver).
            reason_code:
              type: string
              description: 'On a one-off line: its reason.'
            note:
              type: string
              nullable: true
              description: 'On a one-off line: its note.'
            from:
              type: string
              enum:
                - typed_line
                - restocking_box
                - label_policy
              description: 'On a legacy line: what it was built from.'
            original:
              type: object
              additionalProperties: true
              description: >-
                On a line read from what the return stored before: that stored
                line as it was.
            carried_from:
              type: string
              description: >-
                On the return label's line carried over a reopened settlement:
                where it came from.
            label_id:
              type: string
              description: 'On the return label''s line: the label.'
            folded_rule:
              type: object
              additionalProperties: true
              description: >-
                On the restocking fee's line: the automatic restocking rule it
                holds.
            taken_typed_line:
              type: object
              additionalProperties: true
              description: 'On a rule''s line: the typed line of its fee type it holds.'
            absent_from_stored:
              type: boolean
              description: >-
                On an automatic rule's line: the return's stored lines do not
                carry it yet.
            legacy_claim_waiver:
              type: boolean
              description: >-
                On a picked rule's line: the return stored the line as waived
                when the rule was picked, on the customer's claim (not on the
                goods' grade).
        fee_class:
          type: string
          nullable: true
          enum:
            - restocking
            - handling
            - return_shipping
            - processing
            - other
          description: A fee's type; null on a credit.
        label:
          type: string
        gl_account_id:
          type: string
          format: uuid
          nullable: true
          description: >-
            The account a rule names for the fee, else null (the fee type's
            account).
        calc:
          type: object
          required:
            - mode
            - value
            - deferred
          properties:
            mode:
              type: string
              enum:
                - flat
                - percentage
                - label_cost
            value:
              type: number
              description: Dollars for flat; a percentage otherwise.
            deferred:
              type: boolean
              description: >-
                A percentage the goods do not settle yet: computed again when
                they do.
        computed_amount:
          type: number
          description: >-
            What the calculation gives on the goods today, before a person's
            amount and the cap.
        override:
          type: object
          nullable: true
          description: >-
            A person's amount (or waiver) on the line, with its reason, who made
            it and when.
          properties:
            amount:
              type: number
              nullable: true
            waive:
              type: boolean
              description: True when the decision is a waiver.
            reason:
              type: string
            by:
              type: string
              format: uuid
              nullable: true
            at:
              type: string
              format: date-time
              nullable: true
        tax_override:
          type: object
          nullable: true
          description: >-
            A person's tax decision on the line, with its reason, who made it
            and when.
          properties:
            taxable:
              type: boolean
            reason:
              type: string
            by:
              type: string
              format: uuid
              nullable: true
            at:
              type: string
              format: date-time
              nullable: true
        final_amount:
          type: number
          description: What the line charges or credits.
        cap_reduction:
          type: number
          description: >-
            How much the cap (the fees never exceed the goods) took off the
            line.
        taxable:
          type: boolean
        taxable_source:
          type: string
          nullable: true
          enum:
            - rule
            - entity_default
            - exempt_order
            - override
          description: What decided `taxable`; null on a credit.
        exemption:
          type: object
          additionalProperties: true
          description: 'On a fee of a tax-exempt order: the exemption that keeps it untaxed.'
        waived:
          type: boolean
        waiver_source:
          type: string
          nullable: true
          enum:
            - rule_merchant_fault
            - override
          description: 'Who waived it: the rule (a merchant-fault reason) or a person.'
        by:
          type: string
          format: uuid
          nullable: true
          description: The user who added the line, when a person did.
        at:
          type: string
          format: date-time
          nullable: true
          description: When the line was added.
        disclosed_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            The instant of the fee schedule the return took when it was
            authorized, when that schedule carries the line (its rule, or a line
            added before it); null otherwise.
        superseded_by:
          type: string
          description: >-
            On a restocking line the return stored beside its restocking fee:
            the restocking fee's line, which charges it once (this line then
            charges 0.00).
        restocking_bound:
          $ref: '#/components/schemas/ReturnRestockingBound'
        fee_type:
          type: string
          nullable: true
          description: >-
            On a line the return already stores (as `GET /returns/{id}` answers
            it in `adjustment_lines`): the fee type, the same as `fee_class`;
            null on a credit.
        mode:
          type: string
          enum:
            - flat
            - percentage
            - label_cost
          description: >-
            On a stored line: the calculation's mode, or `flat` once a person
            set the line's amount.
        value:
          type: number
          description: >-
            On a stored line: the calculation's value, or the amount a person
            set.
        calculated_amount:
          type: number
          description: 'On a stored line: its figure on the goods when it was stored.'
        gl_account_key:
          type: string
          nullable: true
          description: 'On a stored line: the account the fee type posts to.'
        coupon_id:
          type: string
          format: uuid
          description: 'On a stored line a person picked from a fee rule: the rule.'
        _auto_rule:
          type: string
          format: uuid
          description: 'On a stored line an automatic fee rule charges: the rule.'
        rule_fee_min:
          type: number
          description: 'On a stored rule''s line: the rule''s minimum fee.'
        rule_fee_max:
          type: number
          description: 'On a stored rule''s line: the rule''s maximum fee.'
        deferred_pct:
          type: number
          description: >-
            On a stored line whose percentage the goods do not settle yet: that
            percentage.
        _waived:
          type: boolean
          description: 'On a stored line that is waived: true.'
    ReturnFeePlanRefusal:
      type: object
      description: >-
        One refusal of the fee plan: what it is, and which intent or line it
        names.
      required:
        - code
      properties:
        code:
          type: string
        intent_index:
          type: integer
          description: The intent's place in `intents`, from 0, when an intent caused it.
        field:
          type: string
          description: The intent's field at fault.
        errors:
          type: array
          description: Every problem of the intent's shape (the first is `code`).
          items:
            type: object
            properties:
              code:
                type: string
              field:
                type: string
        key:
          type: string
          description: The line it names.
        keys:
          type: array
          items:
            type: string
          description: >-
            The lines it names (a rule charged on two lines; a handling line
            beside a handling rule's line).
        box_key:
          type: string
          description: >-
            On `restocking_fee_beside_restocking_box`: the restocking fee's
            line.
        coupon_id:
          type: string
          format: uuid
          description: The rule it names.
        goods:
          type: number
          description: 'On `fees_exceed_goods`: the goods the return credits.'
        uncapped_total:
          type: number
          description: 'On `fees_exceed_goods`: the fees before the cap.'
        why:
          type: string
          description: >-
            On `handling_line_may_be_rule_line`: why the line may be the rule's
            own charge.
    ReturnFeeOffer:
      type: object
      description: What can be done about one refusal, on the line it names.
      required:
        - code
        - key
        - actions
      properties:
        code:
          type: string
          description: The refusal it answers.
        key:
          type: string
          nullable: true
          description: >-
            The line to act on; null when the act is the return's (a lower fee,
            or No refund).
        actions:
          type: array
          items:
            type: string
            enum:
              - waive
              - change_amount
              - remove
              - enter_separate_charge
              - waive_box
              - remove_line
              - decide_no_refund
    ReturnFeePlanNotice:
      type: object
      description: >-
        Something the fee plan tells about the return's fees that refuses
        nothing (a rule waived by a merchant-fault reason, a fee schedule the
        return never carried, a restocking fee held to the entity's bounds); its
        other keys name the line or rule.
      required:
        - code
      additionalProperties: true
      properties:
        code:
          type: string
    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.
  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.