Skip to content
Merxian

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.

On this page

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_conflict or InsufficientScope. 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 shape
{
  "errorCode": "InsufficientScope",
  "message": "This API key needs this scope: payments:refund.",
  "fieldErrors": []
}
error.code shape
{
  "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.

read-error.ts
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.

Try refund, payment.succeeded,POST /v1/transactions, orIdempotency-Key.