Skip to main content
Every request retries transient failures automatically and throws a typed error on real ones. Discriminate with instanceof — every error extends ApiError.

Typed errors

All errors carry status, code, message, requestId, and headers. ApiError.generate maps HTTP status to the right subclass:
ClassWhen it throws
ApiErrorBase class for every error below.
BadRequestError400 — malformed request.
AuthenticationError401 — missing or invalid API key.
NotFoundError404 — resource doesn’t exist.
ConflictError409 — idempotency or state conflict (retried first).
ValidationError422 — request body failed validation.
RateLimitError429 — rate limited. Carries retryAfter: number | null.
InternalServerError5xx — server error (retried first).
ConnectionErrorNetwork failure after retries are exhausted.
TimeoutErrorRequest exceeded the configured timeout.
import { UnitPay, ApiError, RateLimitError, ValidationError } from '@unitpay/node';

const unitpay = new UnitPay({ apiKey: process.env.UNITPAY_API_KEY });

try {
  await unitpay.customers.get('cus_missing');
} catch (err) {
  if (err instanceof ValidationError) {
    console.error('bad input', err.code, err.message);
  } else if (err instanceof RateLimitError) {
    console.error('retry after', err.retryAfter, 'seconds');
  } else if (err instanceof ApiError) {
    // catches NotFoundError, ConnectionError, TimeoutError, etc.
    console.error('api error', err.status, err.code, err.requestId);
  } else {
    throw err; // something unexpected
  }
}

Retries & idempotency

Requests retry on 429, 409, and 5xx responses plus network errors, with jittered exponential backoff. A 429 honors the Retry-After header. POST and DELETE requests automatically carry an Idempotency-Key so retries never double-apply — pass your own via RequestOptions.idempotencyKey, or set a idempotencyKeyPrefix on the client. Tune retries and timeout on the constructor or per request:
const unitpay = new UnitPay({
  apiKey: process.env.UNITPAY_API_KEY,
  retries: 2,        // default
  timeout: 30_000,   // ms, default
});

// Per-request override
await unitpay.customers.get('cus_123', {
  timeout: 5_000,
  idempotencyKey: 'my-key',
});

Pagination

List methods return a PagePromise<T>. await it for a single Page<T> ({ data, hasMore, nextCursor, prevCursor }), or for await over it to stream every item across pages. toArray() collects them all.
// One page
const page = await unitpay.customers.list();
console.log(page.data, page.hasMore, page.nextCursor);

// Every item, auto-paginated
for await (const customer of unitpay.customers.list()) {
  console.log(customer.id);
}

// Collect all into an array
const all = await unitpay.customers.list().toArray();

Verifying webhooks

Verify inbound webhook signatures with the static UnitPay.verifyWebhook(body, headers, secret). It validates the Svix signature and returns the parsed, camelCased event; it throws if the signature is invalid.
export async function POST(req: Request) {
  const body = await req.text(); // raw body — do not parse first
  const headers = Object.fromEntries(req.headers);

  const event = await UnitPay.verifyWebhook(
    body,
    headers,
    process.env.UNITPAY_WEBHOOK_SECRET!,
  );

  // handle event…
  return new Response('ok');
}

Observability

Subscribe to raw request/response events for logging or tracing, and read the SDK version:
import { SDK_VERSION } from '@unitpay/node';

unitpay
  .on('request', ({ method, path, attempt }) => console.log(method, path, attempt))
  .on('response', ({ status, requestId }) => console.log(status, requestId));

console.log('sdk', SDK_VERSION);

See also

Customers & subscriptions

Paginated reads.

Introduction

Install and instantiate.