Receiving and inspecting

A warehouse answers three questions: what turned up, what condition it was in, and where it went. It should never be able to answer a fourth one about money.

Give the warehouse a key holding commerce.returns.operations.write and nothing else. It can record arrivals, inspections, and dispositions, and read whatever it needs to do that. It cannot decide a Return, issue a refund, or confirm a resolution. That separation is why receipts and refunds are different objects.

What you are writing#

Receipts and inspections are observations. Once recorded they are never edited or deleted. Correct a mistake by recording a superseding observation with a correction reason, which leaves the original in place. Physical history stays intact, and a reconciliation months later can still see what the floor actually reported at the time.

A disposition is different. It is an authorization: it says where merchandise ended up, and it is what moves stock.

1. Record what arrived#

Bash
curl -X POST https://api.withflintpay.com/v1/returns/ret_1kmn0aExample/receipts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wms-receipt-1042" \
  -d '{
    "receiving_location_id": "loc_1kmn0aExample",
    "source_system": { "source_system_type": "wms", "external_source_id": "wms-receipt-1042" },
    "received_at": "2026-07-29T18:00:00Z",
    "line_items": [
      { "return_line_item_id": "retli_1kmn0aExample", "quantity": 1 }
    ]
  }'

A receipt line takes one of three shapes, and the shape decides what the quantity is allowed to do next.

ShapeSendWhat it means
Matchedreturn_line_item_idThis is the item we expected. Counts toward received_quantity and releases refund timing gates.
Unverifiedunverified_item with a name and SKUSomething arrived and we cannot tell which line it is. Counts toward nothing yet.
Excessmore than the approved quantityMore came back than was approved.

Do not guess an order line to make a parcel fit. Record what you saw:

Bash
curl -X POST https://api.withflintpay.com/v1/returns/ret_1kmn0aExample/receipts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wms-receipt-1043" \
  -d '{
    "receiving_location_id": "loc_1kmn0aExample",
    "source_system": { "source_system_type": "wms", "external_source_id": "wms-receipt-1043" },
    "received_at": "2026-07-29T18:00:00Z",
    "line_items": [
      { "quantity": 1, "unverified_item": { "name": "Blue hoodie", "sku": "HOODIE-BLUE-M" } }
    ]
  }'

Unverified quantity counts toward no line and releases no refund gate. It shows up as a merchandise_exception completion blocker until someone establishes what it is. Once an operator works it out:

Bash
curl -X POST https://api.withflintpay.com/v1/return-receipts/retrc_1kmn0aExample/line-items/retrcli_1kmn0aExample/verify \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wms-verify-1043" \
  -d '{
    "expected_return_revision": 4,
    "return_line_item_id": "retli_1kmn0aExample",
    "verification_reason": "sku_match_confirmed"
  }'

If the receipt itself was wrong, do not edit it. Create a replacement carrying supersedes_return_receipt_id and a correction_reason.

2. Inspect it, when the decision asked you to#

A decision sets is_inspection_required per line. When it is set, received quantity waits for an inspection before it can be dispositioned.

Bash
curl -X POST https://api.withflintpay.com/v1/returns/ret_1kmn0aExample/inspections \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wms-inspection-1042" \
  -d '{
    "return_receipt_id": "retrc_1kmn0aExample",
    "location_id": "loc_1kmn0aExample",
    "source_system": { "source_system_type": "wms", "external_source_id": "wms-insp-1042" },
    "inspected_at": "2026-07-29T19:00:00Z",
    "line_items": [
      {
        "return_receipt_line_item_id": "retrcli_1kmn0aExample",
        "quantity": 1,
        "condition": "opened",
        "acceptance_status": "accepted",
        "finding_codes": ["matches_expected_item"]
      }
    ]
  }'

An inspection always names the receipt it is checking and the location it happened at. Each line answers three separate questions:

  • condition grades the item: new, unopened, opened, used, damaged, defective, incomplete, unknown.
  • finding_codes says what you discovered: matches_expected_item, wrong_item, damaged, defective, used, missing_parts, empty_package, counterfeit_suspected, other.
  • acceptance_status is the call: accepted, rejected, or review_required.

A few words appear in both condition and finding_codes and mean different things there, so read the field name alongside the value. Grading an item damaged is not the same as finding it damaged: one is how it arrived, the other is what you concluded.

Accepted quantity becomes dispositionable and satisfies an after_inspection refund gate.

When the inspector cannot call it#

acceptance_status: "review_required" is the escape hatch for a line the floor should not decide alone, such as a suspected counterfeit or a high-value item. It raises an inspection_review_required completion blocker and holds the Return open until someone resolves it:

Bash
curl -X POST https://api.withflintpay.com/v1/return-inspections/retins_1kmn0aExample/line-items/retinli_1kmn0aExample/decide \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wms-accept-1042" \
  -d '{
    "expected_return_revision": 5,
    "acceptance_status": "accepted",
    "acceptance_decision_reason": "manual_review"
  }'

acceptance_decision_reason is its own short vocabulary: inspection_result, return_policy, manual_review, other. Use /decide to settle a review_required line, or to change an acceptance call that turned out to be wrong.

To skip the check for a line, someone with commerce.returns.decisions.write can waive it through POST /v1/returns/{return_id}/line-items/{return_line_item_id}/waive-inspection. That is a merchant decision, not a warehouse one, which is why the warehouse key cannot do it.

3. Say where it went#

A disposition is what actually moves stock. It targets either a receipt line or an inspection line, never both.

Bash
curl -X POST https://api.withflintpay.com/v1/returns/ret_1kmn0aExample/dispositions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wms-disposition-1042" \
  -d '{
    "return_inspection_line_item_id": "retinli_1kmn0aExample",
    "quantity": 1,
    "disposition_type": "sellable",
    "inventory_location_id": "loc_1kmn0aExample",
    "reason": "inspection_result",
    "occurred_at": "2026-07-29T19:30:00Z"
  }'

Four of the twelve types put units back into inventory, each into a specific quantity bucket at the location you name.

Disposition typeStock effectinventory_location_id
sellableAdds to sellable stockRequired
quality_controlOn hand, not sellableRequired
damagedOn hand, not sellableRequired
quarantinedOn hand, not sellableRequired
lostNone. Records that the unit never arrivedRequired
repair, refurbish, liquidate, donate, discard, return_to_buyerNone. The merchandise leaves without producing stockRejected

Two things in that table catch people out. lost takes a location but moves no stock: it records where the loss was booked, not a bucket that gained units. And the last row rejects inventory_location_id rather than ignoring it, so a donate carrying a location fails rather than quietly succeeding.

Flint will not guess which location gained units, which is why the first five are required rather than defaulted.

One consequence worth planning around: quantity that arrived unverified can only take a non-inventory outcome such as discard, donate, or return_to_buyer. Flint does not know which inventory item to increase, so verify it first if it should go back on sale.

When a disposition fails#

A disposition whose inventory effect fails exposes retry. Use it when the intent has not changed:

Bash
curl -X POST https://api.withflintpay.com/v1/return-dispositions/retdsp_1kmn0aExample/retry \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wms-retry-1042" \
  -d '{
    "expected_return_disposition_revision": 2,
    "reason": "dependency_recovered"
  }'

Retry preserves the disposition and effect identities, so it cannot move stock twice.

When the type, destination, quantity, or source has changed, retrying is wrong. Create a new disposition and name the terminal one it replaces:

Bash
curl -X POST https://api.withflintpay.com/v1/returns/ret_1kmn0aExample/dispositions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wms-replace-1042" \
  -d '{
    "return_receipt_line_item_id": "retrcli_1kmn0aExample",
    "quantity": 1,
    "disposition_type": "return_to_buyer",
    "replaces_return_disposition_id": "retdsp_1kmn0aExample",
    "reason": "warehouse_override",
    "reason_message": "Inventory mapping was unavailable, so the item is going back to the buyer.",
    "occurred_at": "2026-07-29T19:45:00Z"
  }'

Replacement transfers only unused capacity. Once an inventory effect starts processing, Flint refuses both cancellation and replacement, because the stock has already moved.

What you never touch#

The warehouse key cannot confirm a resolution, and that is deliberate. Recording that a parcel arrived is what releases an after_receipt refund gate; it does not authorize the refund. The merchant's resolution decides that, and it can be waiting on things the warehouse cannot see.

If you are looking for why a refund has not gone out after you recorded everything, read completion_blockers on the Return. It names what is still owed and who owes it.

Next steps#

Rate this doc