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

# Refund Order

> Refund an order in full or in part, to the original payment, as store credit, or record a manual refund

# Refund Order

Refunds an order through the same engine the merchant admin and the POS use:
the amount is validated against the order's **refundable balance**, the money
leg runs first (a declined gateway refund moves nothing), accepted lines are
restocked only after the money moved, `financial_status` / the refunded total
roll forward, and `refunds/create` fans out to every app subscribed to it.

Requires the `write_orders` scope.

## Request

```bash cURL theme={null}
curl -X POST "https://api.launchmystore.io/api/v1/orders/ord_abc123/refunds.json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "refund": {
      "amount": 1260,
      "reason": "Return RMA-7K2M-Q9WX",
      "refund_to": "original",
      "restock": true,
      "line_items": [{ "id": "li_xyz789", "quantity": 1 }]
    }
  }'
```

```javascript Node.js theme={null}
const res = await fetch('https://api.launchmystore.io/api/v1/orders/ord_abc123/refunds.json', {
  method: 'POST',
  headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    refund: { amount: 1260, reason: 'Return RMA-7K2M-Q9WX', refund_to: 'original', restock: true, line_items: [{ id: 'li_xyz789', quantity: 1 }] },
  }),
});
const { refund } = await res.json();
```

## Body

The `refund` wrapper is optional — the same fields are accepted at the top level. Shopify's own refund body is accepted as-is too: `refund_line_items[{ line_item_id, quantity, restock_type }]` (only lines whose `restock_type` is not `no_restock` are restocked), `transactions[{ amount }]` (summed into `amount`) and `note` (as `reason`).

**amount** `number`

Amount to refund in the **order's currency**. Omit it to refund the whole remaining balance. Must not exceed the refundable balance (`total_price` minus what was already refunded).

**reason** `string`

Free text, stored on the refund ledger and the staff audit entry.

**refund\_to** `string` (default: original)

Where the money goes:

* `original` — back to the card or account the order was paid with, through the gateway. Available for the gateways LaunchMyStore refunds through (see [Refunds by payment method](#refunds-by-payment-method)); any other payment method returns `400` with what to do instead.
* `store_credit` — records the refund on the order without moving money. Use it when your app issues the value itself, e.g. as a [gift card](https://docs.launchmystore.io/api-reference/gift-cards/create.md).
* `manual` — the shopper was paid back outside LaunchMyStore: in the gateway's own dashboard, by bank transfer, in cash at the counter. Records the refund on the order; no money moves and no till movement is posted.

**restock** `boolean` (default: false)

Put the refunded lines back into inventory. With `line_items`, only those lines (and quantities) are restocked; without, every line on the order. When `restock` is `false` (the default), nothing is restocked — `line_items` then only describe the refund.

**line\_items** `array`

`[{ id, quantity }]` — the line items (their `line_items[].id` from the order) this refund covers; used to limit the restock. `variant_id` (numeric id or UUID) or `product_id` (numeric id or UUID) is accepted in place of `id`.

## Refunds by payment method

| Paid with | `original` | What to do otherwise |
| - | - | - |
| Stripe (online card) | Full and partial refunds, any amount up to the balance | — |
| Stripe Auto Pay (subscriptions) | Full and partial. A subscription order is refunded from the payments made at checkout; a renewal order from its own renewal payment | — |
| Stripe Terminal (in person) | Full and partial | — |
| Any other gateway (Razorpay, PayPal, Mollie, …) | `400` | Refund in the gateway's own dashboard, then record it with `manual` |
| Cash on delivery, manual payment methods, UPI or cash on the till | `400` — there is no card or account to send the money back to | Pay the shopper back in cash or store credit and record it (`manual` / `store_credit`) |

An order that was never paid — still pending, or cancelled before its payment went through — has nothing to refund: every `refund_to` returns `400`.

## Response

Returns `201` with a bare `refund` object (no envelope), like every other `/api/v1` resource.

**refund** `object`

**Refund Object**

**id** `string`

Ledger id: `<order id>:<total refunded after this call>`

**order\_id** `string`

The order

**amount** `number`

What this call actually moved, read back from the ledger (an omitted `amount` resolves to the remaining balance)

**currency** `string`

Order currency

**refund\_to** `string`

`original` | `store_credit` | `manual`

**restocked** `array`

The lines put back into stock by this refund (`[]` when nothing was restocked)

**total\_refunded** `number`

Total refunded on the order so far

**financial\_status** `string`

`partially_refunded` or `refunded`

**created\_at** `string`

ISO 8601

```json theme={null}
{
  "refund": {
    "id": "ord_abc123:1260.00",
    "order_id": "ord_abc123",
    "amount": 1260,
    "currency": "INR",
    "refund_to": "original",
    "reason": "Return RMA-7K2M-Q9WX",
    "restocked": [{ "orderProductId": "li_xyz789", "quantity": 1, "ok": true }],
    "total_refunded": 1260,
    "financial_status": "partially_refunded",
    "created_at": "2026-09-22T15:04:05.000Z"
  }
}
```

## Errors

| HTTP Status | Description |
| - | - |
| `400` | Amount exceeds the refundable balance or is not positive, `refund_to` is invalid, the order was never paid, or `original` was requested for a payment method LaunchMyStore does not refund through (the message says what to do) |
| `502` | The gateway declined the refund (its message is returned). Nothing is recorded — except, when an order's money was taken in several payments, the part the gateway did refund before declining, so it is never paid out twice |
| `401` | Invalid or missing access token |
| `403` | App doesn't have `write_orders` scope |
| `404` | Order not found for this store |

> [!NOTE]
>
> Line-item `price` values on FX-converted orders (an order placed in a currency other than the store's base currency) are returned in the order currency, so a refund computed from `line_items[].price × quantity` always fits the refundable balance.


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