Open a return against a completed order and follow it through to the shopper’s refund.
The Returns API lets you open a return against a completed order and follow it
through to a refund. Returns are whole-order: you return the entire order rather
than individual line items. Once created, a return progresses asynchronously
toward the shopper’s refund.
The Returns API currently supports Shopify orders only. Support for additional
marketplaces will be added over time.
A return moves through the states below and ends in refunded, denied, or
failed. The state field tells you where it is, and which details are present
on the response (a shipping label, a denial reason, or the refund).
Poll GET /api/v1/returns/{returnId}
to track a return until it reaches a terminal state. Once it is refunded, the
response reports the amount returned to the shopper.
Once the return reaches refunded, the response carries the reconciled
timestamps and a refunds array reporting the amount returned to the shopper.Example response:
When a return reaches requires_action, the shopper needs to ship the items
back before the merchant will refund. The response carries a nextAction with a
prepaid return label at nextAction.shipItemsToMerchant.label.url. Forward that
URL to the shopper so they can print the label and send the items in.
Once the shopper ships the items and the merchant issues the refund, the return
advances to refunded on its own. No further calls are required. When a
merchant approves a return without needing the items back, nextAction.type is
no_action_required (with no shipItemsToMerchant payload) and the return skips
straight to processing.
If the original order was paid from your drawdown balance, Rye credits that
balance when it reconciles the refund. Orders paid by other methods refund the
shopper’s own funds and don’t affect your balance.
In staging there is no real merchant to approve a return or issue the refund, so
a real return would sit in requested indefinitely. The test-helper endpoints
let you create a simulated return and drive it through each state yourself, so
you can exercise your integration end to end.
Test helpers are available in staging only (they return 404 in production)
and require an API key with the test_helpers:write scope.
Create a simulated return for an order. Unlike POST /api/v1/returns, this skips
the merchant and hands you direct control over the lifecycle. Only orderId is
required; reason defaults to other, and omitting lineItems returns the
whole order. The response includes the new return’s id.
Approve it to move it forward. ship_items_to_merchant (the default) lands the
return in requires_action with a prepaid label; no_action_required skips
straight to processing.
To exercise the other branches, call /deny or /fail on the return instead.
Denying lands it in denied with a denial object carrying a machine-readable
reason (final_sale, return_period_ended, or the default other) and an
optional note. Failing lands it in failed with a failure object carrying a
code (drawdown_credit_failed, merchant_unreachable, or other) and a
human-readable message.
Every test-helper call returns the updated return in the same shape as
GET /api/v1/returns/{returnId}, so your polling sees exactly what it would in
production.
Get an order’s orderId from the order on its checkout intent
(GET /api/v1/checkout-intents/{id}/order). A return can only be created when
the order has no other active return; once a return reaches a terminal state
(refunded, denied, or failed) you can open another.
Errors come back as a JSON object with a message field and a traceId you can
quote to support. The common cases on the Returns endpoints:
404 Not Found for an unknown returnId or orderId, or a return owned by
another developer. The body looks like { "message": "Not Found" }.
409 Conflict for an out-of-order transition (for example issuing a refund
before the return is approved), or for creating a second return while one is
already active for the order.
400 Bad Request for an invalid body, such as an unrecognized reason or
costBearer, an unknown orderLineItemId, or a non-positive or non-integer
quantity.
Transitions are not idempotent. Replaying a transition on an already-advanced
return returns 409 rather than re-running it, so guard your retries on the
return’s current state rather than blindly resending.