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#
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.
| Shape | Send | What it means |
|---|---|---|
| Matched | return_line_item_id | This is the item we expected. Counts toward received_quantity and releases refund timing gates. |
| Unverified | unverified_item with a name and SKU | Something arrived and we cannot tell which line it is. Counts toward nothing yet. |
| Excess | more than the approved quantity | More came back than was approved. |
Do not guess an order line to make a parcel fit. Record what you saw:
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:
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.
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:
conditiongrades the item:new,unopened,opened,used,damaged,defective,incomplete,unknown.finding_codessays what you discovered:matches_expected_item,wrong_item,damaged,defective,used,missing_parts,empty_package,counterfeit_suspected,other.acceptance_statusis the call:accepted,rejected, orreview_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:
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.
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 type | Stock effect | inventory_location_id |
|---|---|---|
sellable | Adds to sellable stock | Required |
quality_control | On hand, not sellable | Required |
damaged | On hand, not sellable | Required |
quarantined | On hand, not sellable | Required |
lost | None. Records that the unit never arrived | Required |
repair, refurbish, liquidate, donate, discard, return_to_buyer | None. The merchandise leaves without producing stock | Rejected |
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:
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:
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#
- Inventory: where dispositioned stock lands, and the quantity buckets above.
- Return resolutions: what happens on the money side once you are done.
- Return operations API reference: every field on receipts, inspections, and dispositions.
