---
title: "Refund a payment"
description: "Refund a card or Pix payment in full or in part, and follow it to the end. Boleto has no refunds."
url: https://docs.fluveo.com/payments/refunds/
---

> Index of every page: https://docs.fluveo.com/llms.txt. Integrating Fluveo? Read https://docs.fluveo.com/agents/integrate.md first.

> Too long? This page with code in one language only: https://docs.fluveo.com/payments/refunds.curl.md (cURL), https://docs.fluveo.com/payments/refunds.node.md (Node.js), https://docs.fluveo.com/payments/refunds.python.md (Python), https://docs.fluveo.com/payments/refunds.php.md (PHP).

# Refund a payment

Refund a card or Pix payment in full or in part, and follow it to the end. Boleto has no refunds.

A refund gives a buyer back money from a paid payment, and your balance pays for it. One call, `POST /v1/refunds`, makes it. Return the whole payment at once, or smaller amounts over time until the total reaches what was captured. Each payment method allows this much:

| Method | Full refund | Partial refund | Time limit | Where the money goes |
| --- | --- | --- | --- | --- |
| Card | Yes | Yes | None in the API | Back to the buyer's card. The card network takes some days to show it on the buyer's statement. |
| Pix | Yes | Yes | 90 days after the payment | Back to the account that paid, by the payment's own Pix transaction: a *devolução*. |
| Boleto | No | No | Not refundable | See [Boleto has no refunds](https://docs.fluveo.com/payments/refunds.md#boleto). |

## Create a refund

Point at the payment with `payment_intent`, or with its `charge`. Without `amount`, Fluveo returns whatever has not been refunded yet. To give back only part, put the part in `amount`, in the currency's smallest unit: `500` below is 5.00. Make one `Idempotency-Key` for each refund you intend: sending the call again with that key brings back the same refund instead of a second one.

**Refund 5.00 of a payment**

cURL:

```bash
curl https://api.fluveo.com/v1/refunds \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: order-1042-refund-1" \
  -d payment_intent=pi_... \
  -d amount=500 \
  -d reason=requested_by_customer
```

Node.js:

```javascript
const response = await fetch("https://api.fluveo.com/v1/refunds", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk_test_...",
    "Idempotency-Key": "order-1042-refund-1",
  },
  body: new URLSearchParams({
    payment_intent: "pi_...",
    amount: "500",
    reason: "requested_by_customer",
  }),
});
const refund = await response.json();
console.log(refund.id, refund.status);
```

Python:

```python
import requests

response = requests.post(
    "https://api.fluveo.com/v1/refunds",
    headers={
        "Authorization": "Bearer sk_test_...",
        "Idempotency-Key": "order-1042-refund-1",
    },
    data={
        "payment_intent": "pi_...",
        "amount": 500,
        "reason": "requested_by_customer",
    },
)
refund = response.json()
print(refund["id"], refund["status"])
```

PHP:

```php
<?php
$ch = curl_init("https://api.fluveo.com/v1/refunds");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer sk_test_...",
        "Idempotency-Key: order-1042-refund-1",
    ],
    CURLOPT_POSTFIELDS => http_build_query([
        "payment_intent" => "pi_...",
        "amount" => 500,
        "reason" => "requested_by_customer",
    ]),
]);
$refund = json_decode(curl_exec($ch), true);
echo $refund["id"] . " " . $refund["status"];
```

You get back a refund object whose id starts with `re_`. On the charge, `amount_refunded` goes up by that amount, and `refunded` becomes `true` when nothing is left to return.

### The PaymentIntent stays `succeeded`

A refund changes the refund and the charge, never the PaymentIntent. Even after a full refund, the PaymentIntent still answers `status: "succeeded"`, because the payment did happen. To know how much of a payment came back, read its charge (the PaymentIntent's `latest_charge`): `amount_refunded` and `refunded`. Or follow the `charge.refunded` event, which carries that charge each time a refund is made.

**Parameters**

- `payment_intent` (string, optional): The paid `pi_` to refund. Fluveo refunds its latest charge. Send this or `charge`.
- `charge` (string, optional): The charge to refund: a card's `ch_`, a Pix's `py_`. If you send both, the charge must belong to that PaymentIntent.
- `amount` (integer, optional): In the smallest unit of the currency. Default: everything not yet refunded.
- `reason` (string, optional): `requested_by_customer`, `duplicate` or `fraudulent`.
- `metadata` (object, optional): Your own key-value pairs, sent as `metadata[key]=value`, such as `metadata[order_id]=1042`.

Do not send `reverse_transfer` or `refund_application_fee`. Fluveo sets both; any value other than its own is a 400.

A payment you authorized but never captured holds no money to give back yet. [Cancel its PaymentIntent](https://docs.fluveo.com/payments/accept-a-payment.md#manual-capture) to let the hold go.

## Your balance pays for refunds

Fluveo takes each refund out of your store's balance and never lends you the difference. The balance is checked before the refund leaves, and it counts both your `available` and your `pending` money. A refund that returns everything left of a payment, with nothing else of yours on its way out, needs the balance to cover only that amount less the fee Fluveo kept on the payment. So a full refund of a new store's only payment goes through, and the balance then shows that fee below zero.

When the balance falls short, the payment method decides:

- A card refund is accepted anyway and waits. It answers 200 with `status: "pending"` and `pending_reason: "insufficient_funds"`, and Fluveo sends it by itself once your balance covers it.
- A Pix refund is refused with 400 `balance_insufficient`, and nothing goes out.

See [When your balance is too low](https://docs.fluveo.com/payments/refunds.md#balance-too-low) and [Balance and payouts](https://docs.fluveo.com/payments/payouts.md).

## Refund statuses

| Status | Meaning |
| --- | --- |
| `pending` | The refund was sent and the card network or the bank has not confirmed it yet. A Pix refund starts here. A card refund your balance does not cover yet also waits here, unsent, with `pending_reason: "insufficient_funds"`. |
| `succeeded` | The refund went through. |
| `failed` | The refund could not be made; the money is back in your balance (its reservation is released) and `failure_reason` says why. |
| `canceled` | The refund was canceled. |
| `requires_action` | The refund waits on a step outside the API. |

A Pix refund opens in `pending` and waits for the bank. It ends `succeeded` once the bank hands the money back, or `failed` when it cannot. Count it as done only at `succeeded`. A card refund that waits for your balance is sent as soon as the balance covers it; after 45 days without enough balance it ends `failed` with `failure_reason: "insufficient_funds"`. A card refund can also flip from `succeeded` to `failed` later, for instance when the buyer's card no longer exists. So keep listening for `refund.failed`, and close your books on a refund only after the window you care about. To track a refund, use its webhooks or read `GET /v1/refunds/{id}`.

## Events

| Event | When |
| --- | --- |
| `refund.created` + `charge.refunded` | The refund was made, a card refund that waits for your balance included. `charge.refunded` carries the charge with its new `amount_refunded`, which grows even while the refund is still `pending`. These events do not prove success. |
| `refund.updated` + `charge.refund.updated` | The refund's status changed (a pending refund that succeeds sends them with `status: succeeded`), or you changed its metadata. Both carry the refund. |
| `refund.failed` + `charge.refund.updated` | The refund failed. The charge's `amount_refunded` goes back down, and its reserved money is released. |

## Boleto has no refunds

A paid Boleto cannot be refunded through Fluveo. A refund on a Boleto payment, paid or not, answers:

*400 response*

```json
{
  "error": {
    "type": "invalid_request_error",
    "message": "Refunds are not supported on this payment method."
  }
}
```

If you need to give money back for a Boleto purchase, agree on it with the buyer outside Fluveo.

## Limits

- **Pix, 90 days.** A Pix can be refunded only within 90 days of its payment. Later, the refund is refused with `param` `charge`.
- **No more than was paid.** The sum of refunds cannot pass the captured amount. Asking for more answers 400 on `amount`, such as "Refund amount ($10.01) is greater than charge amount ($10.00)", or after a partial refund "Refund amount ($7.01) is greater than unrefunded amount on charge ($7.00)".
- **Already refunded.** Refunding a fully refunded charge without an `amount` answers 400 `charge_already_refunded`.

## Read and change refunds

- `GET /v1/refunds/{id}` reads one refund.
- `GET /v1/refunds` lists refunds, newest first, with `limit` (1 to 100, default 10), `starting_after` or `ending_before`.
- `GET /v1/charges/{charge}/refunds` lists the refunds of one charge.
- `POST /v1/refunds/{id}` changes only `metadata`.

See the [API reference](https://docs.fluveo.com/api/refunds.md) for every field of the refund object.

## Errors you may see

| Error | Why |
| --- | --- |
| 400 `charge_already_refunded` | The whole charge was already refunded. |
| 400 on `amount` | The amount is more than what is left to refund on the charge. |
| 400 "This uncaptured Charge was created by a PaymentIntent ..." | The payment is only authorized: cancel the PaymentIntent instead. |
| 400 "This PaymentIntent (pi_...) does not have a successful charge to refund." | The payment was never confirmed, or it was declined. |
| 400 on `charge`, "You may only specify one of these parameters: charge, payment_intent." | Send `charge` or `payment_intent`, not both. |
| 400 "Refunds are not supported on this payment method." | A Boleto payment. See [Boleto has no refunds](https://docs.fluveo.com/payments/refunds.md#boleto). |
| 400 on `charge` | A Pix paid more than 90 days ago. |
| 400 on `reason` | The reason is not one of the three above, such as `reason=other`. |
| 400 `balance_insufficient` | Your balance does not cover a Pix refund. A card refund waits instead. See [When your balance is too low](https://docs.fluveo.com/payments/refunds.md#balance-too-low). |
| 404 `resource_missing` | The `pi_` or `ch_` is not one of your store's (on `id` for a `ch_`, on `payment_intent` for a `pi_`). |

`POST /v1/refunds/{id}/cancel` answers 400 "Canceling this refund is unsupported.": only a refund in `requires_action` can be canceled, and no Fluveo refund reaches it.

### When your balance is too low

When the balance does not cover a refund (see [Your balance pays for refunds](https://docs.fluveo.com/payments/refunds.md#balance)), what happens depends on the payment method. Either answer is expected, not a bug in your integration, and the same call can go either way depending on the balance.

**A card refund waits.** The answer is 200: a `re_` with `status: "pending"` and `pending_reason: "insufficient_funds"`, followed by `refund.created` and `charge.refunded`. The charge's `amount_refunded` already counts it, and your `available` balance shows it taken out, below zero if need be. Nothing reaches the buyer yet. Fluveo sends the refund by itself once new sales cover it, and it then goes on like any other refund. If 45 days pass first, it ends `failed` with `failure_reason: "insufficient_funds"`, `refund.failed` arrives, and `amount_refunded` goes back down. Do not create the refund again while it waits: it is already counted.

**A Pix refund is refused.** The answer is 400 `balance_insufficient`, and nothing is refunded.

- See it with [`GET /v1/balance`](https://docs.fluveo.com/payments/payouts.md#read-your-balance): compare the refund with your balance.
- Refund an amount the balance covers, or refund again once more payments have raised the balance.
- Send that retry once, with the same `Idempotency-Key`. If it comes back as the same 400 with `Idempotent-Replayed: true`, Fluveo kept that answer for the key: send this one new attempt with a new key. Never loop on new keys.

See [Errors](https://docs.fluveo.com/errors.md) for the shape of every error.

## Test it

1. Take a payment with `pm_card_visa` (see [Accept a payment](https://docs.fluveo.com/payments/accept-a-payment.md)). Even on a new store, your balance covers the refunds below, because the refund that completes the payment needs it to cover only the payment less Fluveo's fee (see [Your balance pays for refunds](https://docs.fluveo.com/payments/refunds.md#balance)).
2. Refund `amount=500`: you get a `re_`, then `refund.created` and `charge.refunded`; the charge shows `amount_refunded: 500`.
3. Refund again with no `amount`: the rest is refunded and the charge shows `refunded: true`. Read the PaymentIntent: it still shows `status: "succeeded"`.
4. Refund once more: `400 charge_already_refunded`.
5. Take a payment with `pm_card_refundFail` and refund it: the refund is created, then fails a little later with `refund.failed`.
6. Send step 2 again with the same `Idempotency-Key`: the same `re_` comes back and nothing is refunded twice.
7. Send `reason=other`: 400 on `reason`. An unknown `pi_...` answers 404.
8. Create a Boleto PaymentIntent (see [Boleto](https://docs.fluveo.com/payments/boleto.md)), then refund it with `payment_intent` and `amount=100`: it answers the 400 above, paid or not.

To refund a test Pix, pay one with the payer email `succeed_immediately@example.com` (see [Pix](https://docs.fluveo.com/payments/pix.md#test-it)), wait for `payment_intent.succeeded`, then refund it with its `payment_intent`. The refund opens in `pending`, as a real Pix refund does.

> **For AI agents:**
>
> Refunds for agents: POST /v1/refunds with payment_intent=pi_... (or charge=ch_...), optional amount in the smallest currency unit and reason in {requested_by_customer, duplicate, fraudulent}, plus an Idempotency-Key unique to that refund. Never retry a refund with a new key: reuse the first key or you may refund twice. Never send reverse_transfer or refund_application_fee. Never refund a Boleto payment: Fluveo answers 400. A refund draws on the store's available plus pending balance; the refund that completes a payment needs that payment less Fluveo's fee, so a full refund of a fresh store's only payment goes through. When the balance falls short, a card refund still answers 200 with status pending and pending_reason insufficient_funds, waits unsent, and goes by itself once the balance covers it (after 45 days it fails with failure_reason insufficient_funds); never create it again. A Pix refund the balance does not cover is refused with 400 balance_insufficient and refunds nothing. Neither is your bug. Tell the user and do not retry it in a loop. A Pix refund can be pending for a while: treat it as done only when status is succeeded. Treat refund.failed as money back in your balance. After a full refund the PaymentIntent stays succeeded: read refunds from the charge (amount_refunded, refunded) or the charge.refunded event.

## Next

- [Handle disputes](https://docs.fluveo.com/payments/disputes.md)
- [Balance and payouts](https://docs.fluveo.com/payments/payouts.md)
- [The Refunds reference](https://docs.fluveo.com/api/refunds.md)
- [Event types](https://docs.fluveo.com/webhooks/events.md)
