Technical concepts
Errors
An error response has an HTTP status and a JSON body with a stable code and a message. Read the status first, then the code.
The error model#
Every error response has three parts:
- An HTTP status. It tells you the class of the error and whether a retry can help.
- A code. A stable, machine-readable string, such as
revision_conflictorInsufficientScope. Branch on it. - A message. A description for a person. It can change at any time. Do not branch on it and do not show it to buyers.
Some errors also name the fields at fault: fieldErrors in one shape, details in the other.
Error bodies do not contain internal detail. For help with a failed request, give Merxian its X-Request-Id header. See Request IDs.
Error body shapes#
The API uses two body shapes today. The error reference lists every code of each family.
| Family | Endpoints | Where the code is |
|---|---|---|
| Request errors | Every endpoint, for checks of the key, the scope, the Idempotency-Key, the size, and the rate |
errorCode |
| Transaction errors | /v1/transactions |
error.code |
| Payment and refund errors | /v1/payments, /v1/refunds |
errorCode |
| Checkout errors | /v1/checkout-sessions, /v1/payment-links |
errorCode |
{
"errorCode": "InsufficientScope",
"message": "This API key needs this scope: payments:refund.",
"fieldErrors": []
}{
"error": {
"code": "revision_conflict",
"message": "The transaction changed after revision 3."
}
}A 404 from Retrieve a payment or Retrieve a refund has an empty body. Handle a body that is not JSON.
Read the code#
This function reads the status and the code from any error response. It is an example for Node.js 18 or later, not part of an SDK.
export interface MerxianError {
status: number
code: string | undefined
message: string | undefined
requestId: string | null
}
export async function readError(response: Response): Promise<MerxianError> {
const text = await response.text()
let body: any = undefined
try {
body = text === '' ? undefined : JSON.parse(text)
} catch {
body = undefined
}
return {
status: response.status,
code: body?.errorCode ?? body?.error?.code,
message: body?.message ?? body?.error?.message,
requestId: response.headers.get('X-Request-Id'),
}
}What to do by status#
| Status | Retry | Action |
|---|---|---|
400, 413 |
No | Fix the request. |
401 |
No | Fix the API key. See Authentication. |
403 |
No | Use a key with the required scope, or check the account state. |
404 |
No | Check the ID and the environment of the key. |
409 |
No | Read the resource, then decide. For an idempotency conflict, use a new key for a new request. |
422 |
No | Read the message. Change the data or the state first. |
429 |
Yes | Wait for Retry-After. See Rate limits. |
500, 502, 503, 504 |
Yes | Retry with the same Idempotency-Key and backoff. See Retries. |
Log every error with its status, its code, and its X-Request-Id. Do not log the API key or the full request headers.