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 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 '{
"return_line_item_id": "retli_1kmn0aExample",
"verification_reason": "sku_match_confirmed"
}'
If the receipt itself was wrong, do not edit it. Before an inspection or disposition consumes it, create a replacement carrying supersedes_return_receipt_id and a correction_reason. A matched receipt under automatic disposition is consumed immediately, so use receipt_disposition_mode: manual when the receiving workflow needs a correction window.
For a known, verified line, the pinned Return policy revision controls what happens next. receipt_disposition_mode: automatic, which is also the default when no revision is pinned, creates a successful sellable disposition at the receipt's receiving location. Tracked merchandise also creates an inventory receipt and movement. Untracked merchandise has no inventory effect. If the line requires inspection, automatic disposition waits for an accepted inspection. manual records the receipt without moving inventory and leaves disposition_required in completion_blockers. An explicit disposition in the same process command always wins.
The remaining stepwise example assumes receipt_disposition_mode: manual, so its accepted inspection leaves the unit available for the explicit disposition in step 3.
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,undetermined. Chooseundeterminedwhen the item's condition could not be assessed.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 satisfies an after_inspection refund gate. Under manual receipt disposition, it also becomes dispositionable. Under automatic receipt disposition, an accepted inspection creates the successful sellable disposition and returns it in generated_dispositions.
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 '{
"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 only to settle a review_required line. Correct an accepted or rejected call by creating a superseding inspection before a disposition consumes it.
To skip the check for a line, someone with commerce.returns.write can waive it through POST /v1/returns/{return_id}/line-items/{return_line_item_id}/waive-inspection. Under automatic receipt disposition, the waiver immediately creates sellable dispositions for eligible received quantity. Under manual receipt disposition, the quantity becomes dispositionable. This 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 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 eleven 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.
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 '{
"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.
