Ask for an invitation
Menu

Endpoint

Request a refund

POST/v1/refunds

Returns 202. With no destination the refund is awaiting_address and its single-use claim link is made (Orrerie emails no payer in this release: the dashboard shows the link, and you give it to the payer); with a destination it is awaiting_approval. Owners and Finance members are notified by email and in the dashboard when it is ready to approve.

Scope a restricted key needs
refunds:write
Publishable key
Not allowed: secret or restricted key only
Idempotency-Key
Required. A request without it is refused.

Parameters

Parameters of Request a refund
ParameterType and placeMeaning
Orrerie-VersionstringheaderAPI version date. Default: the version the key was created with. Example 2026-10-11.
Idempotency-KeyrequiredstringheaderRequired here: a person at your business approves what this request asks for (money out, or a new saved address), and a retry with the same key never asks for it twice. 1 to 255 characters.

Request body

A JSON object. The body is required. A field the call does not list is refused with parameter_unknown.

Request body of Request a refund
FieldTypeMeaning
orderrequiredstringThe order: paid, overpaid, underpaid, or one with a provider refund received.
amountstringIn the pricing currency. Default: everything not yet refunded. Pattern ^\d+(\.\d+)?$.
reasonrequiredstringWith overpayment and no amount, the refund returns the order's excess that waits for a decision (order_not_overpaid when there is none). One of requested_by_customer, duplicate, overpayment, order_cancelled, other.
notestringUp to 500 characters; shown to the approver, not to the payer.
destinationobject{saved_address} or {address, asset}. Absent: the payer is asked through the claim link.
address_confirmed_with_customerbooleanMust be true when destination.address is given. The 24-hour wait then applies (approvable_at).
source_assetstringWhich balance pays. Default: the order's settlement asset, or the payer-network wallet when a provider refund sits there.
rate_basisstringDefault order_rate. One of order_rate, today.
fee_bearerstringWho bears a Relay route fee when the refund crosses assets. Default recipient. One of recipient, merchant.
deduct_network_feebooleanTake the network fee out of the refund instead of the fee account. Default false.
metadataobjectYour own key-value data (rules in Metadata).

Example request

curl

curl https://api.orrerie.com/v1/refunds \
  -H "Authorization: Bearer $ORRERIE_SECRET_KEY" \
  -H "Idempotency-Key: 8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a" \
  -H "Content-Type: application/json" \
  -d '{
  "order": "ord_0zymngngvf12b8b2cra7tb8e",
  "amount": "5.00",
  "reason": "requested_by_customer",
  "note": "One bag arrived damaged"
}'

JavaScript

const response = await fetch("https://api.orrerie.com/v1/refunds", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ORRERIE_SECRET_KEY}`,
    "Idempotency-Key": "8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "order": "ord_0zymngngvf12b8b2cra7tb8e",
    "amount": "5.00",
    "reason": "requested_by_customer",
    "note": "One bag arrived damaged"
  }),
});
const result = await response.json();
if (!response.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
console.log(result);

Python

import os
import requests

response = requests.post(
    "https://api.orrerie.com/v1/refunds",
    headers={"Authorization": f"Bearer {os.environ['ORRERIE_SECRET_KEY']}", "Idempotency-Key": "8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a"},
    json={
        "order": "ord_0zymngngvf12b8b2cra7tb8e",
        "amount": "5.00",
        "reason": "requested_by_customer",
        "note": "One bag arrived damaged",
    },
    timeout=30,
)
result = response.json()
if not response.ok:
    raise RuntimeError(f"{result['error']['code']}: {result['error']['message']}")
print(result)

PHP

<?php
$request = curl_init('https://api.orrerie.com/v1/refunds');
curl_setopt_array($request, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ORRERIE_SECRET_KEY'),
        'Idempotency-Key: 8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'order' => 'ord_0zymngngvf12b8b2cra7tb8e',
        'amount' => '5.00',
        'reason' => 'requested_by_customer',
        'note' => 'One bag arrived damaged',
    ]),
]);
$body = curl_exec($request);
if ($body === false) {
    throw new RuntimeException(curl_error($request));
}
$result = json_decode($body, true);
if (curl_getinfo($request, CURLINFO_HTTP_CODE) >= 400) {
    throw new RuntimeException($result['error']['code'] . ': ' . $result['error']['message']);
}
print_r($result);

Responses

202 Accepted

The request was accepted and is waiting: for a person to approve it in the dashboard, or to be built in the background. Nothing has been signed or sent. The answer is the refund object. Headers: Orrerie-Request-Id.

Example response, 202

{
  "id": "rf_z20ve49yz5hve8f0xsdchj2v",
  "object": "refund",
  "livemode": true,
  "created": "2026-10-12T09:40:00.000Z",
  "updated": "2026-10-12T09:40:00.000Z",
  "status": "awaiting_address",
  "pending_reason": null,
  "order": "ord_0zymngngvf12b8b2cra7tb8e",
  "payment": "pay_2pe50hae6w2jk4sp1rcq5pae",
  "amount": "5.00",
  "currency": "USD",
  "reason": "requested_by_customer",
  "note": "One bag arrived damaged",
  "rate_basis": "order_rate",
  "deduct_network_fee": false,
  "source": {
    "wallet": "wal_bsfw07hymawmda7gg8jsyy3g",
    "asset": "base:usdc",
    "amount": "5.000000"
  },
  "destination": null,
  "address_claim": {
    "sent_to": "jane@example.com",
    "sent_at": "2026-10-12T09:40:00.000Z",
    "claimed_at": null,
    "expires_at": "2026-10-26T09:40:00.000Z"
  },
  "approvable_at": null,
  "delivery": null,
  "network_fee": null,
  "requested_by": {
    "type": "api_key",
    "id": "key_6e3gg2c6ea176xxdek2nar6v"
  },
  "approved_by": null,
  "approval_url": null,
  "approval_expires_at": null,
  "transaction_hash": null,
  "failure_code": null,
  "cancellation_reason": null,
  "approved_at": null,
  "sent_at": null,
  "confirmed_at": null,
  "metadata": {}
}

Any other status: an error

A status of 400 or above answers with the error object. Branch on its code, never on its message.

Errors

Beside the errors every call can answer with (a bad key, a missing scope, the rate limit), this call answers with:

Errors of Request a refund
CodeHTTPMeaning
idempotency_key_required400The endpoint moves money on approval (refunds, payouts, batches, conversions, saved addresses) and needs an Idempotency-Key.
order_not_refundable409The order has nothing paid, or everything paid is already refunded or in a refund request.
refund_amount_exceeds_refundable409More than amount_paid minus refunds confirmed or in request; error.refundable gives the figure.
customer_email_required409The customer or order has no email address.
destination_invalid400The address fails the network's format or checksum, or does not match the asset's network.
destination_blocked403The destination address is on a sanctions list. The request is refused and recorded.
screening_unavailable503The sanctions check could not be made. A failed check is never treated as clean; retry later.
saved_address_not_usable409The saved address is awaiting_approval, waiting (error.usable_at says until when) or removed.
insufficient_balance409The available balance (amount minus reserved) of the source asset is too low; error.available gives it.
fee_account_low409The fee account cannot pay the network fee. error.needed says how much of which coin on which network must be held, for example {"asset": "tron:trx", "amount": "30"}.
rail_unavailable503The rail cannot be used now. error.reason is the exact refusal: relay_unavailable, relay_no_route, relay_amount_too_low, relay_low_liquidity, relay_paused, route_fee_over_cap, provider_route_mismatch, provider_deposit_mismatch, refund_wallet_unavailable, recipient_not_attested, or asset_not_covered; error.available_assets lists what can be used instead. Nothing was created at the provider and nothing was switched.
amount_below_minimum400Below the minimum: US$1.00 for an order payable on the relay rail, the network dust limit on the direct rail or for a payout.
order_not_overpaid409Only an overpaid order with an unresolved excess has an overpayment to keep.
wallet_needs_review409The wallet holds less than Orrerie's records show, because money left it that nothing recorded. Money out from it waits until an Owner has reviewed the wallet in the dashboard.
refund_rate_basis_unavailable409The order's own rate exists only for an asset the order was settled in. From this balance a refund can be paid only at today's value: send rate_basis today.