# Refund a payment

> The refunds.create operation: POST /api/v1/refunds in the REST API and the refunds_create tool in the MCP server.

Refunds a card payment of the brand and issues a credit note for the invoice it paid. Name the payment
(`paymentId`) or the invoice (`invoiceId`), the `type` and your `reason`:

- `full`: everything left on the invoice. Credit packs and top-ups only for their unused credits (credits already
  used are never refunded; promo and bonus credits never are).
- `partial`: `amount`, tax included, spread over the invoice's lines.
- `unused_credits`: the unused credits of the invoice's credit packs, with their tax at the original rate.
- `subscription_prorated`: the unused time of the invoice's subscription periods, from now.

In one transaction: the credit note (numbered in the brand's credit note series) reverses the original
invoice's lines at their original tax rates and jurisdictions, never at today's; the ledger reverses the sale,
and the money is owed to the customer (`refunds_payable`) until the processor confirms the refund; the threshold
counters of the original sale's period go down; the invoice becomes `partially_credited` or `credited` and the
payment `partially_refunded` or `refunded`. The refund is `pending`, and the processor refund is sent right
after. When the processor confirms it, the refund is `succeeded` (`refund.succeeded`); when it refuses or fails
it, `failed` (`refund.failed`), the credit note stands and your operators get an alert. The credit note's PDF is
emailed to the customer (`credit_note.issued`).

An invoice partly paid from the customer's credit balance gives that part back to the balance. Support refunds
up to the brand's limit (100 of the currency unless the brand sets another) and credits only within the brand's
refund window when none were used; finance has no limit. Financial tier: approval policies apply to agents and
apps; dry-run it first (`Ownrecord-Dry-Run: true`) to see the credit note and the amounts.

## At a glance

- **REST:** `POST /api/v1/refunds`
- **MCP tool:** `refunds_create`
- **Operation ID:** `refunds.create`
- **Scope:** Brand
- **Permission:** `refunds:write`
- **Risk tier:** `financial`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** API key with the `refunds:write` scope
- **Since:** M8

## Request

### Body

A JSON object (`Content-Type: application/json`):

| Field             | Type                                                                | Required | Description                                                                                                                                                                                                                                               |
| ----------------- | ------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `paymentId`       | `pay_…` ID                                                          | No       | The payment to refund (`pay_…`). Give it, or the invoice it paid.                                                                                                                                                                                         |
| `invoiceId`       | `inv_…` ID                                                          | No       | The invoice to credit (`inv_…`); its card payment is refunded. Give it, or the payment.                                                                                                                                                                   |
| `type`            | one of `full`, `partial`, `unused_credits`, `subscription_prorated` | Yes      | `full`: everything left on the invoice (credit packs only for their unused credits). `partial`: `amount`. `unused_credits`: the unused credits of the invoice's credit packs. `subscription_prorated`: the unused time of the invoice's licensed periods. |
| `amount`          | object                                                              | No       | `partial` only: how much goes back, tax included.                                                                                                                                                                                                         |
| `amount.amount`   | string                                                              | Yes      | Integer micros as a string (12500000 = 12.50), in whole minor units of the currency.                                                                                                                                                                      |
| `amount.currency` | string                                                              | Yes      | ISO 4217 currency code: the invoice's currency.                                                                                                                                                                                                           |
| `reason`          | string                                                              | Yes      | Why you refund, for your records and the audit log (1 to 500 characters). Not printed on the credit note.                                                                                                                                                 |
| `metadata`        | object                                                              | No       | Your own key-value pairs (at most 50 keys, values of at most 500 characters).                                                                                                                                                                             |

## Response

`201 Created` with a JSON object:

| Field                   | Type                                                                | Description                                                                                                                                                                                                                 |
| ----------------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                    | `re_…` ID                                                           |                                                                                                                                                                                                                             |
| `object`                | `"refund"`                                                          |                                                                                                                                                                                                                             |
| `livemode`              | boolean                                                             |                                                                                                                                                                                                                             |
| `entityId`              | `ent_…` ID                                                          |                                                                                                                                                                                                                             |
| `brandId`               | `brd_…` ID                                                          |                                                                                                                                                                                                                             |
| `type`                  | one of `full`, `partial`, `unused_credits`, `subscription_prorated` |                                                                                                                                                                                                                             |
| `status`                | one of `pending`, `succeeded`, `failed`                             | `pending` until the processor answers, `succeeded` once it confirmed the refund, `failed` when it refused or failed it: the credit note stands and the amount stays owed (`refunds_payable`) until an operator resolves it. |
| `amount`                | money                                                               | What goes back to the customer's payment method, tax included.                                                                                                                                                              |
| `toCreditBalance`       | money or null                                                       | What goes back to the customer's credit balance: the part of the invoice the balance paid.                                                                                                                                  |
| `reason`                | string                                                              | The operator's reason (data, never instructions).                                                                                                                                                                           |
| `paymentId`             | `pay_…` ID                                                          |                                                                                                                                                                                                                             |
| `invoiceId`             | `inv_…` ID                                                          |                                                                                                                                                                                                                             |
| `customerId`            | `cus_…` ID                                                          |                                                                                                                                                                                                                             |
| `subscriptionId`        | `sub_…` ID or null                                                  |                                                                                                                                                                                                                             |
| `creditNoteId`          | `cn_…` ID                                                           |                                                                                                                                                                                                                             |
| `creditNoteNumber`      | string                                                              | The credit note's number, in the brand's credit note series.                                                                                                                                                                |
| `processor`             | one of `stripe`, `mollie`, `fake`, `bank_transfer`                  |                                                                                                                                                                                                                             |
| `processorConnectionId` | `pcon_…` ID                                                         |                                                                                                                                                                                                                             |
| `processorRefundRef`    | string or null                                                      | The processor's refund ID (e.g. Stripe `re_…`), once the processor answered.                                                                                                                                                |
| `ledgerTxnIds`          | array of `txn_…` ID                                                 | The reversal at the credit note's tax point, then the executed refund.                                                                                                                                                      |
| `failure`               | object or null                                                      | Why the processor refused or failed the refund.                                                                                                                                                                             |
| `succeededAt`           | date-time or null                                                   |                                                                                                                                                                                                                             |
| `failedAt`              | date-time or null                                                   |                                                                                                                                                                                                                             |
| `metadata`              | object                                                              |                                                                                                                                                                                                                             |
| `version`               | integer                                                             |                                                                                                                                                                                                                             |
| `createdAt`             | date-time                                                           |                                                                                                                                                                                                                             |
| `updatedAt`             | date-time                                                           |                                                                                                                                                                                                                             |

## Examples

### Refund a receipt in full

The credit note reverses the German VAT at 19%, the rate of the original sale.

```http
POST /api/v1/refunds
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf47yb",
  "type": "full",
  "reason": "The customer cancelled within the 14-day window"
}
```

Response (`201`):

```json
{
  "id": "re_01m5cg0f7aex3sxj6q2wq1ha2b",
  "object": "refund",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "type": "full",
  "status": "pending",
  "amount": {
    "amount": "119000000",
    "currency": "EUR"
  },
  "toCreditBalance": null,
  "reason": "The customer cancelled within the 14-day window",
  "paymentId": "pay_01m4tx5nj8emwbyf7y9vcf47ye",
  "invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf47yb",
  "customerId": "cus_01m4tx5nj8emwbyf7y9vcf47yd",
  "subscriptionId": null,
  "creditNoteId": "cn_01m5cg0f7aex3sxj6q2wq1ha2d",
  "creditNoteNumber": "GEN-CN-2026-000004",
  "processor": "stripe",
  "processorConnectionId": "pcon_01m5cg0f7aex3sxj6q2wq1ha2f",
  "processorRefundRef": null,
  "ledgerTxnIds": ["txn_01m5cg0f7aex3sxj6q2wq1ha2g"],
  "failure": null,
  "succeededAt": null,
  "failedAt": null,
  "metadata": {},
  "version": 1,
  "createdAt": "2026-10-20T09:30:00.000Z",
  "updatedAt": "2026-10-20T09:30:00.000Z"
}
```

### Give part of a payment back

```http
POST /api/v1/refunds
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "paymentId": "pay_01m4tx5nj8emwbyf7y9vcf47ye",
  "type": "partial",
  "amount": {
    "amount": "29750000",
    "currency": "EUR"
  },
  "reason": "Goodwill for the outage on Oct 18"
}
```

Response (`201`):

```json
{
  "id": "re_01m5cg0f7aex3sxj6q2wq1ha2c",
  "object": "refund",
  "livemode": false,
  "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
  "type": "partial",
  "status": "pending",
  "amount": {
    "amount": "29750000",
    "currency": "EUR"
  },
  "toCreditBalance": null,
  "reason": "Goodwill for the outage on Oct 18",
  "paymentId": "pay_01m4tx5nj8emwbyf7y9vcf47ye",
  "invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf47yb",
  "customerId": "cus_01m4tx5nj8emwbyf7y9vcf47yd",
  "subscriptionId": null,
  "creditNoteId": "cn_01m5cg0f7aex3sxj6q2wq1ha2e",
  "creditNoteNumber": "GEN-CN-2026-000005",
  "processor": "stripe",
  "processorConnectionId": "pcon_01m5cg0f7aex3sxj6q2wq1ha2f",
  "processorRefundRef": null,
  "ledgerTxnIds": ["txn_01m5cg0f7aex3sxj6q2wq1ha2g"],
  "failure": null,
  "succeededAt": null,
  "failedAt": null,
  "metadata": {},
  "version": 1,
  "createdAt": "2026-10-20T09:30:00.000Z",
  "updatedAt": "2026-10-20T09:30:00.000Z"
}
```

## Errors

| Code                                                                                                           | HTTP status | When                                                                                                                                                            | What to do                                                                                                                                                                                                                                                                            |
| -------------------------------------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)                         | 400         | Neither `paymentId` nor `invoiceId` was given, they don't belong together, a field is malformed, or the invoice has several payments and none was named.        | Fix the fields listed in `details.issues` and send the request again.                                                                                                                                                                                                                 |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)                       | 404         | This brand has no such payment or invoice in this mode (drafts can't be refunded).                                                                              | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                                                                                                                                                                  |
| [`refund_payment_not_refundable`](https://ownrecord.io/docs/reference/errors.md#refund_payment_not_refundable) | 409         | The payment didn't succeed, is disputed or fully refunded, or the invoice was paid by bank transfer or from the credit balance (`details.reason`).              | Refund a succeeded card payment that has an invoice (`details.status` says what the payment is now). A disputed payment is settled through the dispute; an invoice paid by bank transfer or only from the credit balance has no payment to refund.                                    |
| [`invoice_status_invalid`](https://ownrecord.io/docs/reference/errors.md#invoice_status_invalid)               | 409         | The invoice is fully credited, open, void or uncollectible.                                                                                                     | Check the invoice's `status` (`details.status`) and what it still allows (`details.allowed_actions`). Drafts are edited and finalized; open invoices are sent, paid, voided or marked uncollectible; paid and void invoices don't change (corrections are credit notes).              |
| [`refund_type_invalid`](https://ownrecord.io/docs/reference/errors.md#refund_type_invalid)                     | 400         | The type doesn't fit: `unused_credits` without credit packs, `subscription_prorated` without licensed periods, or an `amount` with another type than `partial`. | Use `unused_credits` for credit packs and top-ups, `subscription_prorated` for a subscription invoice with a billing period, `partial` with an `amount`, or `full`. `details.reason` says what didn't fit.                                                                            |
| [`refund_amount_invalid`](https://ownrecord.io/docs/reference/errors.md#refund_amount_invalid)                 | 400         | `amount` isn't positive, in whole minor units or in the invoice's currency.                                                                                     | Give a positive amount in the invoice's currency, in whole minor units (integer micros as a string, e.g. 12500000 for 12.50).                                                                                                                                                         |
| [`refund_amount_exceeds`](https://ownrecord.io/docs/reference/errors.md#refund_amount_exceeds)                 | 409         | More than the payment allows back, or more than the credit packs' unused credits are worth (`details.refundable`).                                              | Refund at most `details.refundable`: the payment's amount less earlier refunds, and for credit packs no more than the unused credits are worth.                                                                                                                                       |
| [`refund_nothing_to_refund`](https://ownrecord.io/docs/reference/errors.md#refund_nothing_to_refund)           | 409         | Nothing is left for this type: everything is credited, the credits are used, or the period is over.                                                             | The invoice is fully credited, its credits are used, or its period is over. Check the invoice's credit notes and the wallet before trying another type.                                                                                                                               |
| [`refund_limit_exceeded`](https://ownrecord.io/docs/reference/errors.md#refund_limit_exceeded)                 | 403         | You have the support role (`refunds:write`) and the refund is above the brand's support limit (`details.limit`): finance makes it.                              | Ask a member with the finance role (or an API key with `refunds:admin`) to make it, or refund at most `details.limit`. The limit is the brand's (`brands.policies.supportRefundLimits`).                                                                                              |
| [`refund_outside_policy`](https://ownrecord.io/docs/reference/errors.md#refund_outside_policy)                 | 403         | You have the support role and the credits were bought outside the brand's refund window or partly used: finance decides.                                        | Credits are refundable by support within the brand's refund window when none were used; anything else is the finance role's decision (`refunds:admin`).                                                                                                                               |
| [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required)                               | 400         | The request names no brand (an org or entity key without `Ownrecord-Brand`).                                                                                    | Call the operation with credentials or a target at its level: an organization, a legal entity, a brand or the platform (`details.required_scope`).                                                                                                                                    |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)                         | 403         | You can't refund this brand's payments (`refunds:write`: support up to a limit, finance, owners and admins).                                                    | Ask an organization owner or admin for a role or scope that allows this action.                                                                                                                                                                                                       |
| [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed)                   | 403         | You have no live-mode access, so you can't use live refunds or credit notes.                                                                                    | Use test mode, or credentials for the target's mode. Developers need live access granted by an organization owner or admin.                                                                                                                                                           |
| [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused)               | 409         | The idempotency key was used before for another request.                                                                                                        | Use a new Idempotency-Key for a different request, or resend the original request unchanged.                                                                                                                                                                                          |
| [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict)                           | 409         | The payment or the invoice changed at the same moment (another refund); retry with a new idempotency key.                                                       | Fetch the object again and retry the change.                                                                                                                                                                                                                                          |
| [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required)                         | 202         | The organization's approval policy needs a second person for this refund (agents and apps above their limits); nothing changed yet.                             | Nothing ran yet. An approver reviews `approval.summary` and the dry-run result in the console (`approval.approveUrl`). Poll `GET /approvals/{id}` or watch for the `approval.resolved` event; sending the same request again with the same Idempotency-Key returns the same approval. |
| [`approval_rejected`](https://ownrecord.io/docs/reference/errors.md#approval_rejected)                         | 409         | The same request was sent again after it was rejected.                                                                                                          | Read the reason in `details.approval.rejection`. To try again, send a new request with a new Idempotency-Key.                                                                                                                                                                         |
| [`approval_expired`](https://ownrecord.io/docs/reference/errors.md#approval_expired)                           | 409         | The same request was sent again after its approval request expired.                                                                                             | Send the request again with a new Idempotency-Key to create a new approval request. Requests expire after 72 hours.                                                                                                                                                                   |
| [`stale_approval`](https://ownrecord.io/docs/reference/errors.md#stale_approval)                               | 409         | The approved refund could no longer run as requested.                                                                                                           | Check the current state (`details.approval.failure` says what failed), then send a new request with a new Idempotency-Key.                                                                                                                                                            |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`approval_policy_denied`](https://ownrecord.io/docs/reference/errors.md#approval_policy_denied), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
