Refunds
Issue full or partial refunds against captured transactions, track refund lifecycle status, and reconcile refunded amounts with the Tokeflow API.
A refund returns funds for a transaction that has already been captured. With Tokeflow you issue refunds against a single, normalized API — the orchestration layer relays the request to the connected payment provider, tracks the result, and reflects it back on both the refund record and the parent transaction.
Tokeflow standardizes refunds across providers: one request shape, one status lifecycle, one reconciliation model. The connected payment provider performs the actual movement of funds.
Tokeflow is a payment orchestration layer. It records, routes, and tracks refund requests and normalizes the result; the connected payment provider executes the refund and settles funds.
How refunds work
Every refund is a child of a captured transaction. You can refund the full remaining captured amount, or issue several partial refunds over time until the transaction is fully refunded.
- Full refund — omit
amountto refund the entire remaining captured amount. - Partial refund — pass
amount(in minor units) to refund less than the remaining captured amount. The remaining balance stays captured and can be refunded later. - Multiple partial refunds — repeat partial refunds until the remaining captured amount reaches zero. Each call creates a new refund record.
A refund can only be initiated when the parent transaction is in authorized or refund_pending status. Once a refund is initiated, the transaction moves to refund_pending while the provider confirms the result.
Refund results are typically asynchronous. The synchronous response confirms the request was accepted (refund_pending); subscribe to webhooks to learn the final outcome instead of polling.
Refund lifecycle
A refund record has its own status, independent of the parent transaction.
| Status | Meaning |
|---|---|
pending | Refund initiated, not yet confirmed by the provider. |
succeeded | Refund confirmed by the provider. |
failed | Refund failed at the provider; see failure_reason. |
cancelled | Refund was cancelled before processing. |
How refund status maps to the transaction
The parent transaction reflects the aggregate refund state:
- While any refund is
pending, the transaction isrefund_pending. - When the captured amount has been fully refunded, the transaction becomes
refunded. - When only part of the captured amount has been refunded, the transaction becomes
partially_refunded.
Endpoints
POST/api/v1/transactions/:id/refund
Refund a captured transaction. Supports full and multiple partial refunds.
Auth: Organization key (with merchant_id) or Merchant key. Required scope: transactions:write.
Refund a previously captured transaction. Omit amount for a full refund of the remaining captured amount, or pass amount for a partial refund.
The transaction must be in authorized or refund_pending status, and the refund amount must not exceed the remaining captured amount. Other states return 422 business_rule_error; an amount over the remaining balance returns 400 validation_error.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Transaction ID (tx_…) to refund. |
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
merchant_id | string | Conditional | Target merchant (mrc_…). Required when using an Organization key; inferred from a Merchant key. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
amount | integer | No | Amount to refund in minor units. Omit for a full refund. Must be ≥ 1 and ≤ the remaining captured amount. |
reason | enum | No | Why the refund was issued. One of duplicate, fraudulent, requested_by_customer. |
Send an Idempotency-Key header on refund requests so an automatic retry never issues a second refund. Replaying the same key returns the original result. See Idempotency & rate limits.
curl -X POST "https://api.tokeflow.com/api/v1/transactions/tx_777/refund" \
-H "Authorization: Bearer sk_live_mer_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1c9a7e-2b4d-4e0a-9c6f-7d1e2a3b4c5d" \
-d '{
"amount": 5000,
"reason": "requested_by_customer"
}'For a full refund, omit amount:
curl -X POST "https://api.tokeflow.com/api/v1/transactions/tx_777/refund" \
-H "Authorization: Bearer sk_live_mer_…" \
-H "Content-Type: application/json" \
-d '{ "reason": "duplicate" }'Response 200 OK
The response confirms the refund was accepted and echoes the reconciliation totals on the transaction. Money fields are in minor units.
{
"success": true,
"data": {
"id": "tx_777",
"refund_id": "ref_abc123",
"status": "refund_pending",
"amount_captured": 15000,
"amount_refunded": 5000,
"total_refunded": 5000,
"updated_at": "2026-01-15T12:30:00.000Z"
},
"request_id": "req_8f3c2a1b9d4e",
"timestamp": "2026-01-15T12:30:00.000Z"
}| Field | Type | Description |
|---|---|---|
id | string | Parent transaction ID (tx_…). |
refund_id | string | The refund record created by this request (ref_…). |
status | enum | Transaction status after the request — refund_pending. |
amount_captured | integer | Total amount captured on the transaction, unchanged by the refund. |
amount_refunded | integer | Amount refunded by this request. |
total_refunded | integer | Cumulative amount refunded across all refunds on the transaction. |
updated_at | string | ISO 8601 UTC timestamp of the update. |
On a replayed Idempotency-Key, this endpoint returns the original result with HTTP 200 rather than creating a new refund.
GET/api/v1/transactions/:id/refunds
List all refunds for a transaction, newest first.
Auth: Organization key (with merchant_id) or Merchant key. Required scope: transactions:read.
Use this to view refund history, check the status of partial refunds, or correlate refund events with webhook notifications.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Transaction ID (tx_…). |
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (1-indexed). Default 1, min 1. |
limit | integer | No | Items per page. Default 20, max 100. |
merchant_id | string | Conditional | Target merchant (mrc_…). Required with an Organization key. |
curl "https://api.tokeflow.com/api/v1/transactions/tx_777/refunds?page=1&limit=20" \
-H "Authorization: Bearer sk_live_mer_…" \
-H "Accept: application/json"Response 200 OK
{
"success": true,
"data": [
{
"id": "ref_def456",
"payment_transaction_id": "tx_777",
"amount": 4000,
"currency": "BRL",
"status": "succeeded",
"reason": "requested_by_customer",
"provider_refund_id": "rfnd_7Qx9KpL2",
"failure_reason": null,
"created_at": "2026-01-15T13:10:00.000Z",
"updated_at": "2026-01-15T13:11:42.000Z"
},
{
"id": "ref_abc123",
"payment_transaction_id": "tx_777",
"amount": 5000,
"currency": "BRL",
"status": "succeeded",
"reason": "requested_by_customer",
"provider_refund_id": "rfnd_3Mt0VbN8",
"failure_reason": null,
"created_at": "2026-01-15T12:30:00.000Z",
"updated_at": "2026-01-15T12:31:05.000Z"
}
],
"meta": {
"pagination": {
"page": 1,
"limit": 20,
"total": 2,
"total_pages": 1,
"has_next": false,
"has_prev": false
}
},
"request_id": "req_5a2f7b1c8e0d",
"timestamp": "2026-01-15T13:12:00.000Z"
}GET/api/v1/transactions/:id/refunds/:refundId
Retrieve a single refund.
Auth: Organization key (with merchant_id) or Merchant key. Required scope: transactions:read.
Use this in support flows — for example, to inspect a refund after a webhook notification, or when a refund fails or takes time to settle.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Transaction ID (tx_…). |
refundId | string | Yes | Refund ID (ref_…). |
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
merchant_id | string | Conditional | Target merchant (mrc_…). Required with an Organization key. |
curl "https://api.tokeflow.com/api/v1/transactions/tx_777/refunds/ref_abc123" \
-H "Authorization: Bearer sk_live_mer_…" \
-H "Accept: application/json"Response 200 OK
{
"success": true,
"data": {
"id": "ref_abc123",
"payment_transaction_id": "tx_777",
"amount": 5000,
"currency": "BRL",
"status": "succeeded",
"reason": "requested_by_customer",
"provider_refund_id": "rfnd_3Mt0VbN8",
"failure_reason": null,
"created_at": "2026-01-15T12:30:00.000Z",
"updated_at": "2026-01-15T12:31:05.000Z"
},
"request_id": "req_9c4e1d7a2b6f",
"timestamp": "2026-01-15T12:32:00.000Z"
}The refund object
| Field | Type | Description |
|---|---|---|
id | string | Refund ID (ref_…). |
payment_transaction_id | string | Parent transaction ID (tx_…). |
amount | integer | Refund amount in minor units. |
currency | string | ISO 4217 currency code (e.g. BRL). |
status | enum | pending, succeeded, failed, or cancelled. |
reason | enum | null | duplicate, fraudulent, or requested_by_customer. null if not provided. |
provider_refund_id | string | null | Opaque identifier for the refund at the connected provider. Treat as an opaque string. null until assigned. |
failure_reason | string | null | Human-readable failure detail when status is failed; otherwise null. |
created_at | string | ISO 8601 UTC creation timestamp. |
updated_at | string | ISO 8601 UTC last-update timestamp. |
Errors
Refund endpoints use the standard error envelope.
| HTTP | Type | When |
|---|---|---|
400 | validation_error | amount exceeds the remaining captured amount, or is malformed. |
401 | authentication_error | Missing or invalid API key. |
403 | authorization_error | Key lacks the required scope, or the transaction is outside the key's scope. |
404 | not_found_error | Transaction or refund not found, or not owned by the merchant. |
409 | conflict_error | A conflicting refund is already in progress. |
422 | business_rule_error | Transaction is not in authorized or refund_pending status. |
429 | rate_limit_error | Per-key rate limit exceeded; back off and retry. |
{
"error": {
"type": "business_rule_error",
"code": "TRANSACTION_NOT_REFUNDABLE",
"message": "Transaction must be in 'authorized' or 'refund_pending' status to be refunded.",
"details": { "current_status": "failed" },
"request_id": "req_2b8d4f6a1c3e",
"timestamp": "2026-01-15T12:40:00.000Z"
}
}Related
- Transactions — create, capture, and inspect transactions.
- Webhooks — receive
transaction.refundedandtransaction.partially_refundedevents. - Idempotency & rate limits — make refund requests safe to retry.
- Errors — error envelope and types.