Skip to content

Refund an order

Credit money back on a captured order with POST /refunds, in full or in part, with return fees where the market supports them and allocated to the right shipment.

Refunding returns money you have already captured. It’s a separate call from cancelling, which releases money you never took — if any part of the order is captured, refund is the only way back. This page covers refunding in full and refunding in part. You need a captured order and a credential for its environment.

Paths are relative to your base URL: https://api.playground.kustom.co/ while testing, https://api.kustom.co/ in production.

Send a Kustom-Idempotency-Key on every write. Every POST and PATCH to Order Management moves money or changes the order. Send a Kustom-Idempotency-Key header with one UUID per operation, and reuse it when you retry, so a call repeated after a timeout runs once instead of twice. Idempotency and retries has the rules and the retry schedule.

Read the order first. refunded_amount may not exceed captured_amount, and that’s the constraint most refused refunds hit.

Refund everything you captured when the whole order comes back.

POST /ordermanagement/v1/orders/{order_id}/refunds
Kustom-Idempotency-Key: 4c2e8a51-7d3f-4b6e-9a10-5f8d2c7e1b94
Content-Type: application/json
{
"refunded_amount": 20000,
"reference": "4c2e8a51-7d3f-4b6e-9a10-5f8d2c7e1b94",
"description": "Returned: the whole order"
}

refunded_amount is the only required field. description is shown to the customer, so write it for them rather than for your logs.

A successful refund returns 201 Created with an empty body and the new refund’s id in two headers, Refund-Id and Location. Use that Location as returned rather than assembling one. Then re-read the order: refunded_amount should have risen by what you refunded.

Refund part of what you captured — a returned item, say — with a smaller refunded_amount, and send the order lines being returned. The lines are optional in general but are what drives allocation when the order shipped in parts.

POST /ordermanagement/v1/orders/{order_id}/refunds
Kustom-Idempotency-Key: 9dfcd4b6-9428-4835-ad93-3720ea808608
Content-Type: application/json
{
"refunded_amount": 10000,
"reference": "9dfcd4b6-9428-4835-ad93-3720ea808608",
"description": "Returned: one pair of sneakers",
"order_lines": [
{
"type": "physical",
"reference": "B24680",
"name": "Sneakers",
"quantity": 1,
"quantity_unit": "pcs",
"unit_price": 10000,
"total_amount": 10000,
"tax_rate": 2500,
"total_tax_amount": 2000
}
]
}

Supported in Austria, Germany, Denmark, Finland, France, the Netherlands, Norway and Sweden, and only on an order that was fully captured in a single capture. Model the fee as an order line of type return_fee with a negative amount, and make the lines sum to refunded_amount:

{
"refunded_amount": 9500,
"reference": "1786c73b-a2be-4d42-a9f7-ff1c4bca5ccc",
"description": "Returned: one T-shirt, less return postage",
"order_lines": [
{ "type": "physical", "name": "T-Shirt", "quantity": 1, "unit_price": 10000, "total_amount": 10000 },
{ "type": "return_fee", "name": "Return fee", "quantity": 1, "unit_price": -500, "total_amount": -500 }
]
}

Send order lines whose reference values are the same ones you sent on the capture that contained the returned goods. Kustom uses those references to allocate the credit to the right invoice.

Say an order shipped as two captures:

{
"captured_amount": 10000,
"description": "Capture A — T-shirt and jeans",
"order_lines": [
{ "type": "physical", "reference": "A12345", "name": "T-shirt", "quantity": 1, "unit_price": 3000, "total_amount": 3000 },
{ "type": "physical", "reference": "A98765", "name": "Jeans", "quantity": 1, "unit_price": 7000, "total_amount": 7000 }
]
}
{
"captured_amount": 10000,
"description": "Capture B — sneakers",
"order_lines": [
{ "type": "physical", "reference": "B24680", "name": "Sneakers", "quantity": 1, "unit_price": 10000, "total_amount": 10000 }
]
}

The customer returns the sneakers, so the refund must carry B24680 — the reference from Capture B, the capture the sneakers were in. That’s the refund body above. Sending A12345 instead, or sending no lines at all, credits the wrong invoice.

The lines must sum exactly to refunded_amount, and refunded_amount must be no greater than the captured_amount of the capture being refunded. Check in the Kustom Portal that the credit landed on the invoice you expected: the API call succeeds either way.

If you’re not going to capture the rest of an order, release the remaining authorization rather than leaving it to expire:

POST /ordermanagement/v1/orders/{order_id}/release-remaining-authorization
Kustom-Idempotency-Key: 2471e418-d208-49b4-bce4-d2cd45dbc1d4

No body. Returns 204 No Content. This isn’t a refund — nothing captured moves — it just frees the reservation on the customer’s side.

Errors carry error_code, error_messages and a correlation_id. Branch on the code, and quote the correlation id to support.

The one to recognize is REFUND_NOT_ALLOWED: the order doesn’t have that much captured amount left to return — nothing has been captured yet, the captured amount is already fully refunded, or refunded_amount is larger than what remains. It describes the order’s state, not your request, so retrying the same call can’t succeed — capture first, or check captured_amount against what you’ve already returned.

This page is also available as Markdown — append .md to its URL — and is indexed in llms.txt.