Skip to content

TypeScript — getting started

The TypeScript SDK family is four packages that share a versioning policy and snap together:

PackageWhat it isRuntime
@paylera/sdkTyped HTTP client for the Paylera REST API.Node ≥ 18, Bun, Deno, Cloudflare Workers, Vercel Edge, browsers.
@paylera/react<PayleraProvider> + 12 hooks.React 18 / 19.
@paylera/serverMerchant-backend relay + webhook receivers.Node ≥ 18. Express, Next.js, Hono, Fastify.
@paylera/cliDeclarative paylera.config.ts deploy CLI.Node ≥ 18.

If you only need to make server-side calls (a worker job, a CRON, a CI script), @paylera/sdk alone is enough. Browser-facing apps need @paylera/server (to hide the secret token) plus, if React, @paylera/react.

Install

Terminal window
npm install @paylera/sdk
# or, all four:
npm install @paylera/sdk @paylera/react @paylera/server @paylera/cli

OpenTelemetry instrumentation is opt-in — install the peer dep only if you want spans + metrics:

Terminal window
npm install @opentelemetry/api

@paylera/sdk — core HTTP client

import { PayleraClient } from "@paylera/sdk";
const paylera = new PayleraClient(process.env.PAYLERA_API_TOKEN!);
const customer = await paylera.customers.create({
type: "business",
name: "Acme",
currency: "USD",
});
const decision = await paylera.check({
feature_code: "api_calls",
customer_id: customer.id,
required_usage: 1,
});
if (decision.allowed) {
// serve the request
await paylera.track({ feature_code: "api_calls", value: 1 });
}

Token-prefix base-URL routing

The client derives the base URL from the token prefix:

Token prefixBase URL
pl_live_*https://api.paylera.dev
pl_test_*https://api.test.paylera.dev
anything elseexplicit opts.baseUrl required

An explicit baseUrl always wins (self-hosted Paylera, staging environments).

Construction options

new PayleraClient(apiToken, {
baseUrl, // override token-prefix routing
fetch, // (input, init) => Promise<Response> — defaults to global fetch
tracer, // OpenTelemetry Tracer; spans emitted as `Paylera.<Op>`
meter, // OpenTelemetry Meter; emits `paylera.requests.{total,duration}`
retry, // RetryConfig | false (see below)
defaultHeaders, // attached to every request
apiVersion, // default "2026-05-01"
userAgent, // appended to outgoing User-Agent
sleep, // (ms) => Promise<void> — override for tests
});

Idempotency-Key auto-stamping

Every endpoint flagged with a required Idempotency-Key in the OpenAPI spec gets one auto-stamped (UUID v7). Override per call:

await paylera.track(
{ feature_code: "api_calls", value: 1 },
{ idempotencyKey: "req-abc123" },
);

Auto-stamped endpoints: POST /v1/customers, POST /v1/subscriptions, POST /v1/subscriptions/{id}/cancel-at-period-end, POST /v1/subscriptions/{id}/change-plan, POST /v1/payment-methods, POST /v1/payment-methods/{id}/set-default, POST /v1/coupons/{code}/redeem, POST /v1/attach, POST /v1/track.

Retry policy

Default: 5 attempts, exponential backoff (200 ms → 400 ms → 800 ms → 1.6 s → 3.2 s), only on 5xx + 429 (4xx is never retried). On 429 the Retry-After response header overrides the computed backoff.

new PayleraClient(token, { retry: false }); // disable
new PayleraClient(token, { // tune
retry: {
maxAttempts: 3,
initialDelayMs: 100,
multiplier: 3,
maxDelayMs: 5_000,
jitter: 0.2,
},
});
await paylera.check({ feature_code: "x" }, { retry: false }); // per call

Typed errors (RFC 9457)

Every non-2xx response is parsed as RFC 9457 problem-details and thrown as the matching subclass:

import {
PayleraError,
PayleraAuthenticationError,
PayleraAuthorizationError,
PayleraNotFoundError,
PayleraPreconditionFailedError,
PayleraRateLimitError,
PayleraIdempotencyConflictError,
PayleraIdempotencyInProgressError,
PayleraServerError,
} from "@paylera/sdk";
try {
await paylera.subscriptions.changePlan(id, { new_plan_id: "..." });
} catch (err) {
if (err instanceof PayleraRateLimitError) {
await sleep(err.retryAfter ?? 1);
} else if (err instanceof PayleraError) {
console.error(err.code, err.requestId, err.problem);
}
}

The type URI is the primary discriminator; HTTP status is the fallback.

Endpoint surface

The client exposes one method per public API endpoint (36 operations):

  • paylera.customers.* — create, get, list, update, listInvoices, listPaymentMethods
  • paylera.products.* — create, get, list, update
  • paylera.plans.* — create, get, list, update, addComponent
  • paylera.subscriptions.* — create, get, list, update, cancel, pause, resume, changePlan, entitlements
  • paylera.paymentMethods.* — attach, get, detach, setDefault
  • paylera.coupons.* — redeem
  • paylera.payoutBatches.* — list, get, listItems
  • paylera.attach(…), paylera.check(…), paylera.track(…)

Types for every request body and response payload are generated from the OpenAPI spec — import type { Schemas } from "@paylera/sdk".

OpenTelemetry

Spans are named Paylera.<Op> (e.g. Paylera.Check, Paylera.Attach). Attribute keys conform to the cross-language SDK protocol:

KeyValue
paylera.endpointpath template, e.g. /v1/check
paylera.api_mode"live" / "test" / "custom"
paylera.idempotency_keyfirst 8 chars of the key
paylera.problem.typeRFC 9457 type on error

Metrics:

  • paylera.requests.total (counter, {request}) labelled by endpoint, status_code, api_mode
  • paylera.requests.duration (histogram, s) same labels

Both span + metric emission are no-op when no tracer/meter is supplied.

import { trace, metrics } from "@opentelemetry/api";
const paylera = new PayleraClient(token, {
tracer: trace.getTracer("my-app"),
meter: metrics.getMeter("my-app"),
});

Next steps

Examples

Three end-to-end example apps live under examples/typescript/ in the monorepo:

  • examples/typescript/nextjs/ — Next.js 15 App Router with relay route, SSR entitlements, gated feature.
  • examples/typescript/express/ — Express server mounting the relay; exercise with curl.
  • examples/typescript/webhook-receiver/ — standalone Express webhook receiver.