Errors
When a request fails, the Merchant API returns a non-2xx HTTP status and a consistent JSON error envelope. The code is a stable, machine-readable string you can branch on; the message is a human-readable explanation meant for logs and developers, not end users.
The error envelope
Every error has the same shape:
Error envelope
{
"error": {
"code": "invalid_amount",
"message": "amount must be a decimal string"
}
}
Branch on error.code, never on error.message. The message is localized by the request's
Accept-Language header (English by default) and may change wording; the code is part of the API
contract.
HTTP status codes
- Name
400- Type
- Bad Request
- Description
The request is malformed — invalid JSON body, a bad amount, or a missing required field.
- Name
401- Type
- Unauthorized
- Description
Authentication failed. See Authentication for the specific codes (
missing_auth,stale_request,bad_signature, …).
- Name
403- Type
- Forbidden
- Description
Authenticated, but not allowed to perform this action.
- Name
404- Type
- Not Found
- Description
The resource does not exist, or is not owned by your account.
- Name
409- Type
- Conflict
- Description
The request conflicts with the resource's current state — e.g. refunding an order that is not refundable.
- Name
429- Type
- Too Many Requests
- Description
You are being rate limited. Back off and retry later.
- Name
500- Type
- Internal Server Error
- Description
Something went wrong on our side. Safe to retry idempotent requests.
- Name
503- Type
- Service Unavailable
- Description
A dependency is temporarily unavailable — e.g. an on-chain channel whose deposit-address pool is momentarily exhausted. Retry shortly.
Common error codes
These are the codes you are most likely to encounter across the Merchant API. Endpoint pages list the codes specific to each operation.
- Name
invalid_body- Type
- 400
- Description
The request body is not valid JSON.
- Name
invalid_amount- Type
- 400
- Description
An amount field is not a valid decimal string.
- Name
order_not_found- Type
- 404
- Description
No order with that id belongs to your account.
- Name
not_refundable- Type
- 409
- Description
Only a paid (or partially refunded) order can be refunded.
- Name
refund_unsupported- Type
- 409
- Description
On-chain (non-custodial) orders settle directly to your address and cannot be refunded through the platform.
- Name
no_deposit_address- Type
- 503
- Description
No deposit address is available for an on-chain charge right now — the channel's receiving-address pool is empty or exhausted. Retry shortly.
- Name
unknown_channel- Type
- 400
- Description
The
channelcode is not enabled for your account.
- Name
channel_or_network_required- Type
- 400
- Description
A charge needs either an explicit
channelor anetwork(withcurrency) for routing.
- Name
no_route- Type
- 400
- Description
No enabled channel collects the requested (
network,currency) pair.
- Name
pay_currency_mismatch- Type
- 400
- Description
On a routed charge,
metadata.pay_currencycontradicts the requestednetwork— drop one of the two.
- Name
settlement_address_required- Type
- 400
- Description
A live direct-settlement charge needs your settlement address for the order's chain kind configured first. See Account.
- Name
payment_mode_disabled- Type
- 403
- Description
The channel's settlement mode is disabled for your account. Contact the platform operator.
Handling errors
Check the HTTP status first, then read error.code to decide what to do.
Retry 5xx and 429 with backoff; treat 4xx (other than 429) as a
request you must fix before retrying.
Node.js
const res = await pgFetch('POST', '/api/v1/orders/ord_2Zx9/refund', {
amount: '10.00',
})
if (!res.ok) {
const { error } = await res.json()
switch (error.code) {
case 'not_refundable':
// order is not in a refundable state
break
case 'refund_unsupported':
// on-chain order — refund off-platform
break
default:
throw new Error(`${res.status} ${error.code}: ${error.message}`)
}
}