Returns
/v1/returns. A return exists only under the order it belongs to. Every route on this page is /v1/orders/{id}/…, and there is no way to list returns across orders from a top-level path — filter orders instead.The four steps
POST /v1/orders/{id}/return → registered
POST /v1/orders/{id}/returns/{rid}/receive → received (skippable)
POST /v1/orders/{id}/returns/{rid}/complete → completed (books quantity_returned)
POST /v1/orders/{id}/returns/{rid}/reject → rejected (books nothing)
Only the complete step moves any quantity. Registering and receiving are announcements.
1. Register the case
curl -X POST "https://api.revenexx.com/v1/orders/{id}/return" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{
"reason": "Wrong variant delivered",
"positions": [ { "position": 10, "quantity": 2, "restock": true } ]
}'
Answers 201 with the stored return — the row is created, not an order updated. It draws a return number from the tenant's return range.
| Detail | |
|---|---|
| Guards | Positions are guarded against what actually shipped and has not already come back, so a return cannot exceed the goods that left. |
restock | Per position: whether the item is expected to be sellable again. Recorded now, acted on only when the return completes. |
Omitting positions | Registers everything still returnable, in full — the "the customer sent the whole delivery back" case. |
| Books | Nothing. quantity_returned stays where it is and the order does not move. |
| Allowed on | A completed order. Refused on a cancelled one. |
2. Receive the goods
curl -X POST "https://api.revenexx.com/v1/orders/{id}/returns/{rid}/receive" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{}'
The goods-in scan: the parcel is physically back and nobody has decided yet whether the customer gets their money. It moves the return from registered to received and stamps received_at, which is what separates announced from here on a returns worklist.
It books nothing, so a return that arrives damaged can still be rejected afterwards.
Only a registered return can be received; a second call, or one against a settled return, is a 422. This step is skippable — a return may be completed straight from registered where a merchant does not scan goods in.
3a. Complete — accept it
curl -X POST "https://api.revenexx.com/v1/orders/{id}/returns/{rid}/complete" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"resolution":"refund"}'
The only step that moves quantity_returned. The goods are taken back on the order's books, completed_at is stamped by the server, and the settlement is recorded.
resolution is validated against the settlement words this app publishes:
refund partial_refund replacement repair store_credit
Read the live list from GET /v1/orders/vocabularies/return-resolutions. Anything else is refused rather than stored as a word no reader knows, and it is checked before the positions are booked, so a rejected value leaves nothing behind.
restock array names what to hand to POST /v1/inventories/restock; payment travels through POST /v1/orders/{id}/payment-status.Once completed the return is final — receive, complete and reject all refuse afterwards.
3b. Reject — close it against the customer
curl -X POST "https://api.revenexx.com/v1/orders/{id}/returns/{rid}/reject" \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "Content-Type: application/json" \
-d '{"resolution":"wear_and_tear"}'
The goods came back used, outside the window, or were never covered. The return moves to rejected and rejected_at is stamped.
The order is untouched — the quantities still count as shipped and not returned, which is the point: a rejected return must leave the books exactly as they were. Nothing is booked onto the positions.
resolution is validated against the refusal words wear_and_tear and not_returnable. reason stays free text — a sentence about this one return rather than a value out of a set — and is what is stored when no resolution is named.
Rejection is final, and it says nothing about where the physical goods go.
The return row
| Field | Meaning |
|---|---|
number | Drawn from the return range. |
order_id | The order it belongs to. |
status | registered, received, completed, rejected. |
positions | What is coming back, with quantities and the restock flag. |
reason | Free text. |
resolution | One of the published settlement or refusal words. |
registered_at, received_at, completed_at, rejected_at | Server-stamped as each step runs. |
The orchestration you own
A complete return, end to end, is four calls in three apps:
POST /v1/orders/{id}/returns/{rid}/complete → books quantity_returned, answers `restock`
POST /v1/inventories/restock → puts the sellable goods back
POST /v1/payments/{id}/refund → moves the money
POST /v1/orders/{id}/payment-status → records that it moved
Each is separate because each fails independently, and a merchant's policy on which of them happens differs. Drive them from a workflow reacting to the return's event, not from a single request.
Where to go next
- Orders — the state machine a return sits alongside.
- Stock — the restock call.
- Payment lifecycle — the refund call.
- Events and comments — where each return step shows up.