> ## Documentation Index
> Fetch the complete documentation index at: https://docs-test.rye.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Returns

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

> [!NOTE]
>
> The Returns API currently supports Shopify orders only. Support for additional
> marketplaces will be added over time.

## Creating a return

Call [`POST /api/v1/returns`](https://docs-test.rye.com/docs/api-v2/api-reference/returns/create-return) with
the Rye `orderId` and a `reason` (such as `defective`, `wrong_item`, or
`unwanted`). The order must be `completed`.

```bash curl theme={null}
curl --request POST \
  --url https://api.rye.com/api/v1/returns \
  --header "Authorization: Bearer $RYE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{ "orderId": "order_8c2b…", "reason": "defective" }'
```

```javascript TypeScript SDK theme={null}
import CheckoutIntents from 'checkout-intents';

const client = new CheckoutIntents({ apiKey: process.env['RYE_API_KEY'] });

const ret = await client.returns.create({
  orderId: 'order_8c2b…',
  reason: 'defective',
});
```

```python Python SDK theme={null}
import os
from checkout_intents import CheckoutIntents

client = CheckoutIntents(api_key=os.environ["RYE_API_KEY"])

ret = client.returns.create(
    order_id="order_8c2b…",
    reason="defective",
)
```

A new return starts in `requested`, with only `requestedAt` set on its timeline.
The [reference](https://docs-test.rye.com/docs/api-v2/api-reference/returns/create-return.md) has the full request
and response shape.

Example response:

```json theme={null}
{
  "id": "ret_3f9a…",
  "state": "requested",
  "orderId": "order_8c2b…",
  "checkoutIntentId": "ci_…",
  "reason": "defective",
  "timeline": {
    "requestedAt": "2026-06-19T17:04:11.220Z"
  },
  "createdAt": "2026-06-19T17:04:11.220Z",
  "updatedAt": "2026-06-19T17:04:11.220Z"
}
```

`reason` accepts any of `defective`, `wrong_item`, `unwanted`, `color`,
`not_as_described`, `size_too_large`, `size_too_small`, `style`, or `other`.

## Lifecycle

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

```mermaid theme={null}
stateDiagram-v2
    [*] --> requested: Create return
    requested --> requires_action: Approved, items shipped back
    requested --> processing: Approved, no shipping needed
    requested --> denied: Declined
    requires_action --> refunded: Refund issued
    processing --> refunded: Refund issued
    requested --> failed
    requires_action --> failed
    processing --> failed
    refunded --> [*]
    denied --> [*]
    failed --> [*]
```

## Following a return

Poll [`GET /api/v1/returns/{returnId}`](https://docs-test.rye.com/docs/api-v2/api-reference/returns/get-return)
to track a return until it reaches a terminal state. Once it is `refunded`, the
response reports the amount returned to the shopper.

```bash curl theme={null}
curl --request GET \
  --url https://api.rye.com/api/v1/returns/ret_3f9a… \
  --header "Authorization: Bearer $RYE_API_KEY"
```

```javascript TypeScript SDK theme={null}
import CheckoutIntents from 'checkout-intents';

const client = new CheckoutIntents({ apiKey: process.env['RYE_API_KEY'] });

const ret = await client.returns.retrieve('ret_3f9a…');
```

```python Python SDK theme={null}
import os
from checkout_intents import CheckoutIntents

client = CheckoutIntents(api_key=os.environ["RYE_API_KEY"])

ret = client.returns.retrieve("ret_3f9a…")
```

Once the return reaches `refunded`, the response carries the reconciled
timestamps and a `refunds` array reporting the amount returned to the shopper.

Example response:

```json theme={null}
{
  "id": "ret_3f9a…",
  "state": "refunded",
  "orderId": "order_8c2b…",
  "checkoutIntentId": "ci_…",
  "reason": "defective",
  "timeline": {
    "requestedAt": "2026-06-19T17:04:11.220Z",
    "returnApprovedAt": "2026-06-19T18:10:02.000Z",
    "refundIssuedAt": "2026-06-20T09:30:44.000Z",
    "refundedAt": "2026-06-20T09:31:10.000Z"
  },
  "nextAction": {
    "type": "ship_items_to_merchant",
    "shipItemsToMerchant": {
      "label": { "url": "https://…/return-label.pdf" }
    }
  },
  "refunds": [
    {
      "id": "refund_…",
      "refundedAt": "2026-06-20T09:31:10.000Z",
      "shopperRefundTotal": { "amountSubunits": 2300, "currencyCode": "USD" }
    }
  ],
  "createdAt": "2026-06-19T17:04:11.220Z",
  "updatedAt": "2026-06-20T09:31:10.000Z"
}
```

`shopperRefundTotal.amountSubunits` is in minor units (cents for USD), so `2300`
is \$23.00.

### Handling `requires_action`

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.

```json theme={null}
{
  "id": "ret_3f9a…",
  "state": "requires_action",
  "orderId": "order_8c2b…",
  "checkoutIntentId": "ci_…",
  "reason": "defective",
  "timeline": {
    "requestedAt": "2026-06-19T17:04:11.220Z",
    "returnApprovedAt": "2026-06-19T18:10:02.000Z"
  },
  "nextAction": {
    "type": "ship_items_to_merchant",
    "shipItemsToMerchant": {
      "label": { "url": "https://…/return-label.pdf" }
    }
  },
  "createdAt": "2026-06-19T17:04:11.220Z",
  "updatedAt": "2026-06-19T18:10:02.000Z"
}
```

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

> [!NOTE]
>
> 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.

## Testing in staging

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.

> [!NOTE]
>
> 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

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

```bash curl theme={null}
curl --request POST \
  --url https://staging.api.rye.com/api/v1/test-helpers/returns \
  --header "Authorization: Bearer $RYE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{ "orderId": "order_8c2b…" }'
```

```javascript TypeScript SDK theme={null}
import CheckoutIntents from 'checkout-intents';

const client = new CheckoutIntents({
  apiKey: process.env['RYE_API_KEY'],
  baseURL: 'https://staging.api.rye.com',
});

const ret = await client.testHelpers.returns.create({ orderId: 'order_8c2b…' });
```

```python Python SDK theme={null}
import os
from checkout_intents import CheckoutIntents

client = CheckoutIntents(
    api_key=os.environ["RYE_API_KEY"],
    base_url="https://staging.api.rye.com",
)

ret = client.test_helpers.returns.create(order_id="order_8c2b…")
```

Example response:

```json theme={null}
{
  "id": "ret_3f9a…",
  "state": "requested",
  "orderId": "order_8c2b…",
  "checkoutIntentId": "ci_…",
  "reason": "other",
  "timeline": {
    "requestedAt": "2026-06-19T17:04:11.220Z"
  },
  "createdAt": "2026-06-19T17:04:11.220Z",
  "updatedAt": "2026-06-19T17:04:11.220Z"
}
```

### Approve the return

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

```bash curl theme={null}
curl --request POST \
  --url https://staging.api.rye.com/api/v1/test-helpers/returns/ret_3f9a…/approve \
  --header "Authorization: Bearer $RYE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{ "nextAction": "ship_items_to_merchant" }'
```

```javascript TypeScript SDK theme={null}
const ret = await client.testHelpers.returns.approve('ret_3f9a…', {
  nextAction: 'ship_items_to_merchant',
});
```

```python Python SDK theme={null}
ret = client.test_helpers.returns.approve(
    "ret_3f9a…",
    next_action="ship_items_to_merchant",
)
```

Example response:

```json theme={null}
{
  "id": "ret_3f9a…",
  "state": "requires_action",
  "orderId": "order_8c2b…",
  "checkoutIntentId": "ci_…",
  "reason": "other",
  "timeline": {
    "requestedAt": "2026-06-19T17:04:11.220Z",
    "returnApprovedAt": "2026-06-19T18:10:02.000Z"
  },
  "nextAction": {
    "type": "ship_items_to_merchant",
    "shipItemsToMerchant": {
      "label": { "url": "https://…/return-label.pdf" }
    }
  },
  "createdAt": "2026-06-19T17:04:11.220Z",
  "updatedAt": "2026-06-19T18:10:02.000Z"
}
```

### Issue the refund

Issue the refund to reach the terminal `refunded` state. `costBearer` controls
who absorbs the refund and defaults to `shopper`.

```bash curl theme={null}
curl --request POST \
  --url https://staging.api.rye.com/api/v1/test-helpers/returns/ret_3f9a…/refund \
  --header "Authorization: Bearer $RYE_API_KEY"
```

```javascript TypeScript SDK theme={null}
const ret = await client.testHelpers.returns.refund('ret_3f9a…');
```

```python Python SDK theme={null}
ret = client.test_helpers.returns.refund("ret_3f9a…")
```

Example response:

```json theme={null}
{
  "id": "ret_3f9a…",
  "state": "refunded",
  "orderId": "order_8c2b…",
  "checkoutIntentId": "ci_…",
  "reason": "other",
  "timeline": {
    "requestedAt": "2026-06-19T17:04:11.220Z",
    "returnApprovedAt": "2026-06-19T18:10:02.000Z",
    "refundIssuedAt": "2026-06-20T09:30:44.000Z",
    "refundedAt": "2026-06-20T09:31:10.000Z"
  },
  "nextAction": {
    "type": "ship_items_to_merchant",
    "shipItemsToMerchant": {
      "label": { "url": "https://…/return-label.pdf" }
    }
  },
  "refunds": [
    {
      "id": "refund_…",
      "refundedAt": "2026-06-20T09:31:10.000Z",
      "shopperRefundTotal": { "amountSubunits": 2300, "currencyCode": "USD" }
    }
  ],
  "createdAt": "2026-06-19T17:04:11.220Z",
  "updatedAt": "2026-06-20T09:31:10.000Z"
}
```

### The deny and fail branches

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

```bash curl theme={null}
curl --request POST \
  --url https://staging.api.rye.com/api/v1/test-helpers/returns/ret_3f9a…/deny \
  --header "Authorization: Bearer $RYE_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{ "reason": "final_sale", "note": "Past the return window" }'
```

```javascript TypeScript SDK theme={null}
const ret = await client.testHelpers.returns.deny('ret_3f9a…', {
  reason: 'final_sale',
  note: 'Past the return window',
});
```

```python Python SDK theme={null}
ret = client.test_helpers.returns.deny(
    "ret_3f9a…",
    reason="final_sale",
    note="Past the return window",
)
```

To simulate a failed return, call `/fail`:

```bash curl theme={null}
curl --request POST \
  --url https://staging.api.rye.com/api/v1/test-helpers/returns/ret_3f9a…/fail \
  --header "Authorization: Bearer $RYE_API_KEY"
```

```javascript TypeScript SDK theme={null}
const ret = await client.testHelpers.returns.fail('ret_3f9a…');
```

```python Python SDK theme={null}
ret = client.test_helpers.returns.fail("ret_3f9a…")
```

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.

> [!TIP]
>
> 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.

### Error responses

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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.