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

# Returns, Exchanges & Claims

> How Spree models what happens after a customer receives an order — three separate records for money back, different items, and something gone wrong.

## Overview

Three things can happen after a customer receives an order, and Spree models each on its own:

| Record       | What happened                                 | Outcome                                                                  |
| ------------ | --------------------------------------------- | ------------------------------------------------------------------------ |
| **Return**   | Customer sends items back                     | Money back                                                               |
| **Exchange** | Customer sends items back                     | Different items                                                          |
| **Claim**    | Item arrived damaged, wrong, or never arrived | Refund, replacement, or both — usually without asking for the goods back |

Keeping them separate matters most for claims. Without a record for "it arrived smashed", merchants end up either creating a manual order or opening a return they immediately mark received — which records the wrong thing and makes damage reporting impossible.

```mermaid theme={"theme":"night-owl"}
erDiagram
    Order ||--o{ Return : "has many"
    Order ||--o{ Exchange : "has many"
    Order ||--o{ Claim : "has many"
    Return ||--o{ ReturnLineItem : "has many"
    Return ||--o{ ShippingLabel : "prepays"
    Return ||--o{ Delivery : "comes back as"
    Return }o--|| StockLocation : "received at"

    Return {
        string number
        string status
        datetime received_at
    }
    Claim {
        string number
        string status
        string resolution
    }
```

## Statuses

| Record       | Statuses                                                           |
| ------------ | ------------------------------------------------------------------ |
| **Return**   | `requested` → `approved` → `received` → `refunded`, or `canceled`  |
| **Exchange** | `requested` → `approved` → `received` → `fulfilled`, or `canceled` |
| **Claim**    | `open` → `approved` → `resolved`, or `denied` / `canceled`         |

Each step is its own API call, because each one needs information the last one didn't have — what actually turned up, how much to refund, what to send instead.

## Customer self-service

Customers can open a return or a claim on their own order and follow its progress. Approving, receiving and refunding stay with the merchant.

<CodeGroup>
  ```typescript Store SDK theme={"theme":"night-owl"}
  // Request a return — items refer to units that shipped, not cart line items
  const returnRequest = await client.orders.returns.create('or_xxx', {
    items: [{ fulfillment_item_id: 'fi_xxx', quantity: 1 }],
    reason_id: 'rsn_xxx',
    memo: 'Too small',
  })

  // Report a problem
  const claim = await client.orders.claims.create('or_xxx', {
    items: [{ line_item_id: 'li_xxx', quantity: 1, description: 'Arrived cracked' }],
    reason_id: 'clr_xxx',
  })

  // Follow progress
  const returns = await client.orders.returns.list('or_xxx')
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -X POST 'https://api.mystore.com/api/v3/store/orders/or_xxx/returns' \
    -H 'X-Spree-API-Key: pk_xxx' \
    -H 'X-Spree-Token: order_token_xxx' \
    -H 'Content-Type: application/json' \
    -d '{ "items": [{ "fulfillment_item_id": "fi_xxx", "quantity": 1 }],
          "memo": "Too small" }'
  ```
</CodeGroup>

Claim types are `damaged`, `missing`, `wrong_item` and `other` out of the box, and a store can add its own.

## Processing a return

<CodeGroup>
  ```typescript Admin SDK theme={"theme":"night-owl"}
  // Approve
  await adminClient.orders.returns.approve('or_xxx', 'ret_xxx')

  // Record what actually arrived
  await adminClient.orders.returns.receive('or_xxx', 'ret_xxx', {
    items: [
      { return_line_item_id: 'rli_1', quantity: 2, resellable: true },
      { return_line_item_id: 'rli_2', quantity: 1, resellable: false },
    ],
  })

  // Refund
  await adminClient.orders.returns.refund('or_xxx', 'ret_xxx', {
    amount: '24.99',
    refund_method: 'original_payment',
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/returns/ret_xxx/receive' \
    -H 'X-Spree-API-Key: sk_xxx' \
    -H 'Content-Type: application/json' \
    -d '{ "items": [{ "return_line_item_id": "rli_1", "quantity": 2, "resellable": true }] }'
  ```
</CodeGroup>

Receiving takes the quantities the warehouse actually counted, because partial and damaged returns are normal rather than exceptional. A customer says three items are coming; two arrive; one of those can't be sold again. Only resellable goods go back into stock.

Leave `items` out to receive everything as requested. Refunds default to whatever the return is still owed, and can go back to the original payment method or to store credit.

### Where the goods come back to

A return is received at a stock location. By default that is wherever the goods
shipped from, which is right until a merchant inspects and restocks returns at
one processing centre — so locations carry a `returns_enabled` flag, and a
return routes to one that accepts them: the seller's for a seller's goods, the
operator's own otherwise. Pass `stock_location_id` when opening the return to
choose explicitly.

### Prepaid return labels

A return can carry its own postage. The label is bought against the same
carrier account the outbound parcel shipped on, and the shipment is booked in
reverse — from the customer's address back to the return's stock location.

Buying one mints a delivery on the return, so the inbound parcel is tracked the
same way an outbound one is. A delivery reporting arrival never receives the
return: arrival is not inspection, and what actually turned up is still counted
by hand.

<CodeGroup>
  ```typescript Admin SDK theme={"theme":"night-owl"}
  // Buy prepaid postage for the parcel coming back
  const label = await adminClient.orders.returns.labels.create('or_xxx', 'ret_xxx')

  // Or record one bought elsewhere
  await adminClient.orders.returns.labels.create('or_xxx', 'ret_xxx', {
    file: signedBlobId,
    tracking_number: '1Z999AA10123456784',
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -X POST 'https://api.mystore.com/api/v3/admin/orders/or_xxx/returns/ret_xxx/labels' \
    -H 'X-Spree-API-Key: sk_xxx'
  ```
</CodeGroup>

Customers download the label from the storefront, so a return request can end
with "print this and drop it off" rather than an email exchange. See
[shipping labels](/docs/developer/core-concepts/fulfillments#shipping-labels) for
how postage records work in general.

## Resolving a claim

What to do about a claim is decided when you resolve it, not when the customer opens it — merchants usually decide once they've seen the photos.

<CodeGroup>
  ```typescript Admin SDK theme={"theme":"night-owl"}
  await adminClient.orders.claims.approve('or_xxx', 'clm_xxx')

  await adminClient.orders.claims.resolve('or_xxx', 'clm_xxx', {
    resolution: 'refund_and_replacement',   // or 'refund', 'replacement'
    refund_method: 'store_credit',
    replacement_line_item_ids: ['li_xxx'],
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/claims/clm_xxx/approve' \
    -H 'X-Spree-API-Key: sk_xxx'

  curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/claims/clm_xxx/resolve' \
    -H 'X-Spree-API-Key: sk_xxx' \
    -H 'Content-Type: application/json' \
    -d '{
      "resolution": "refund_and_replacement",
      "refund_method": "store_credit",
      "replacement_line_item_ids": ["li_xxx"]
    }'
  ```
</CodeGroup>

A replacement creates a new [fulfillment](/docs/developer/core-concepts/fulfillments) on the original order, so the customer doesn't have to place a second one.

## Exchanges

An exchange works like a return, but ends by sending different items instead of refunding:

<CodeGroup>
  ```typescript Admin SDK theme={"theme":"night-owl"}
  await adminClient.orders.exchanges.approve('or_xxx', 'exch_xxx')
  await adminClient.orders.exchanges.receive('or_xxx', 'exch_xxx')
  await adminClient.orders.exchanges.fulfill('or_xxx', 'exch_xxx')
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/exchanges/exch_xxx/approve' \
    -H 'X-Spree-API-Key: sk_xxx'

  curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/exchanges/exch_xxx/receive' \
    -H 'X-Spree-API-Key: sk_xxx'

  curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/exchanges/exch_xxx/fulfill' \
    -H 'X-Spree-API-Key: sk_xxx'
  ```
</CodeGroup>

## Reporting

Because each one is its own record, you can query them directly:

<CodeGroup>
  ```typescript Admin SDK theme={"theme":"night-owl"}
  // Everything awaiting action
  const pending = await adminClient.returns.list({ status_eq: 'approved' })

  // Claims opened this month
  const claims = await adminClient.claims.list({
    created_at_gt: '2026-08-01',
  })
  ```

  ```bash cURL theme={"theme":"night-owl"}
  curl 'https://api.mystore.com/api/v3/admin/returns?q[status_eq]=approved' \
    -H 'X-Spree-API-Key: sk_xxx'

  curl 'https://api.mystore.com/api/v3/admin/claims?q[created_at_gt]=2026-08-01' \
    -H 'X-Spree-API-Key: sk_xxx'
  ```
</CodeGroup>

## Return policy

Spree ships no built-in return window. Whether a customer may open a return is store policy, and it's checked when the return is created — so you can hold customers to a 30-day window while letting staff make an exception for a good customer, and vary the rule by market where local law requires it.

Set the return window per market, or express a more specific rule in your own application. See [Configuration](/docs/developer/customization/configuration) and [Services & Workflows](/docs/developer/customization/workflows).

## Events

Each step publishes an [event](/docs/developer/core-concepts/events) — `return.received`, `return.refunded`, `exchange.fulfilled`, `claim.resolved` — which also reach [webhooks](/docs/developer/core-concepts/webhooks).

Returns and claims also update the order's [payment status](/docs/developer/core-concepts/orders#statuses), so a refunded order reflects it without any manual bookkeeping.

## Related

* [Orders](/docs/developer/core-concepts/orders) — payment status after refunds
* [Fulfillments](/docs/developer/core-concepts/fulfillments) — replacement deliveries
* [Inventory](/docs/developer/core-concepts/inventory) — putting returned goods back in stock
