Skip to main content
When a request fails, the API returns a JSON error object with a consistent structure across all endpoints. Every non-2xx response includes an error key at the top level so you can handle failures uniformly regardless of which resource you are working with.

Error Response Structure

{
  "error": {
    "type": "invalid_request",
    "code": "invalid_body",
    "message": "fiatAmount: Required",
    "param": "fiatAmount",
    "requestId": "req_01HXYZ"
  }
}
type
string
required
High-level error category. Use this for branching your error-handling logic. See the error types table below.
code
string
required
Machine-readable code that identifies the specific failure within a category. Stable across API versions — safe to switch on in code.
message
string
required
Human-readable description of what went wrong. Intended for logs and debugging — do not display this string verbatim to end users.
param
string
Present on invalid_request errors. Names the specific request field that failed validation, e.g. "fiatAmount" or "successUrl".
requestId
string
required
Unique identifier for this request. Include this value whenever you contact support so the request can be traced on the server side.

Error Types

Each error type maps to a fixed HTTP status code. Match on type for high-level branching, and code when you need to handle a specific scenario.
TypeHTTP StatusDescription
authentication401API key is missing, invalid, or revoked
permission403API key lacks the required scope for this operation
invalid_request400Request body or query parameters failed validation
not_found404The requested resource does not exist or does not belong to your account
conflict409Idempotency key reused with a different request body
rate_limited429Too many requests; back off and retry
server500Internal server error on our side

Common Error Codes

invalid_body
invalid_request
The request body failed schema validation. The param field identifies which property is missing or malformed. Fix the field and resend — do not retry the same payload.
invalid_query
invalid_request
A query string parameter failed validation (e.g. limit is out of range). The param field names the offending parameter.
The payment link referenced by linkId has been deactivated. You cannot create a checkout session against an inactive link. Reactivate the link from the dashboard or use a different one.
not_found
not_found
The resource with the given ID either does not exist or belongs to a different account. Verify the ID and that you are using the correct API key.

Handling Errors with the TypeScript SDK

The SDK maps every error.type from the API to a strongly-typed error class. You can instanceof-check against any of the exported classes, or catch the base CheckoutError to handle everything in one branch.
import {
  CheckoutError,
  AuthenticationError,
  PermissionError,
  InvalidRequestError,
  NotFoundError,
  ConflictError,
  RateLimitError,
  ServerError,
} from "@your-org/checkout-sdk";

try {
  const link = await client.paymentLinks.create({
    title: "Order #123",
    fiatAmount: "10.00",
  });
} catch (err) {
  if (err instanceof AuthenticationError) {
    // 401 — your API key is missing, expired, or revoked
    console.error("Check your API key configuration.");
  } else if (err instanceof PermissionError) {
    // 403 — key exists but lacks the required scope
    console.error("This key does not have links:write scope.");
  } else if (err instanceof InvalidRequestError) {
    // 400 — validation failure; err.param tells you which field
    console.error(`Invalid field: ${err.param}${err.message}`);
  } else if (err instanceof NotFoundError) {
    // 404 — resource not found or wrong account
    console.error("Resource not found.");
  } else if (err instanceof ConflictError) {
    // 409 — idempotency key reused with a different body
    console.error("Idempotency conflict — check if the original request succeeded.");
  } else if (err instanceof RateLimitError) {
    // 429 — back off and retry
    await delay(1000);
    // retry with exponential backoff...
  } else if (err instanceof ServerError) {
    // 500 — internal server error; safe to retry with backoff
    console.error("Server error — retry or contact support with the requestId.");
  }
  throw err;
}
Every error class extends CheckoutError and exposes the following properties: type, code, status (HTTP status code), message, param (optional), and requestId (optional).

Handling Errors Without the SDK

If you are calling the API directly over HTTP without the SDK, check res.ok and parse the error object from the JSON body:
const res = await fetch("https://your-domain.com/api/v1/payment_links", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ title: "Coffee", fiatAmount: "5.00" }),
});

if (!res.ok) {
  const { error } = await res.json();
  console.error(`[${error.type}] ${error.code}: ${error.message}`);
  // e.g. "[invalid_request] invalid_body: fiatAmount: Required"
}

Retry Strategy

Not every error warrants a retry. Use the table below to decide:
StatusAction
429 — rate limitedRetry with exponential backoff starting at 1 second (e.g. 1s → 2s → 4s → 8s)
500 — server errorRetry up to 3 times with backoff; surface the error to the user if it persists
409 — conflictDo not retry with the same body. Check whether the original request succeeded before taking any other action
400 — invalid requestDo not retry. The request is malformed — fix the payload first
401 — authenticationDo not retry. Verify your API key is correct and has not been revoked
403 — permissionDo not retry. The key lacks the required scope — update scopes from the dashboard
404 — not foundDo not retry. Verify the resource ID and that you are using the right API key
Never retry a 409 conflict automatically. The idempotency key collision means a request with that key was already received by the server. Retrying with the same key returns the original response; retrying with a new key creates a duplicate. First confirm the status of the original operation.
Add jitter to your backoff intervals to avoid thundering-herd problems when multiple processes retry simultaneously. A simple approach: delay = baseDelay * 2^attempt + Math.random() * 100.

The requestId Field

Every error response includes a requestId in the error object. This ID uniquely identifies your request in the server logs. When you open a support issue or file a bug report, always include the requestId — it is the fastest way to pinpoint what happened on the server side.
} catch (err) {
  if (err instanceof CheckoutError) {
    console.error(`Request ${err.requestId} failed: [${err.type}] ${err.code}`);
  }
}