Returns

A return on revenexx is a sub-resource of an order, not a top-level entity — register, receive, then complete or reject, with only the complete step booking anything.
There is no /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

text
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

Request
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
GuardsPositions are guarded against what actually shipped and has not already come back, so a return cannot exceed the goods that left.
restockPer position: whether the item is expected to be sellable again. Recorded now, acted on only when the return completes.
Omitting positionsRegisters everything still returnable, in full — the "the customer sent the whole delivery back" case.
BooksNothing. quantity_returned stays where it is and the order does not move.
Allowed onA completed order. Refused on a cancelled one.

2. Receive the goods

Request
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

Request
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:

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

It does not refund money and does not put stock back. The response's 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

Request
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

FieldMeaning
numberDrawn from the return range.
order_idThe order it belongs to.
statusregistered, received, completed, rejected.
positionsWhat is coming back, with quantities and the restock flag.
reasonFree text.
resolutionOne of the published settlement or refusal words.
registered_at, received_at, completed_at, rejected_atServer-stamped as each step runs.

The orchestration you own

A complete return, end to end, is four calls in three apps:

text
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

Was this page helpful?