TypeScript SDK
Typed access to payments, commerce, QR helpers, webhook verification, and the x402 gate.
@mayarin/sdk is Mayarin’s hand-written TypeScript client. It pins the API revision, adds /v1, generates idempotency keys for writes, maps failures into one typed error, and separates secret-key server usage from publishable-key browser usage.
bun add @mayarin/sdkThree entry points
| Import | Key | Surface |
|---|---|---|
@mayarin/sdk | secret (sk_…) | commerce and payment in full |
@mayarin/sdk/browser | publishable (pk_…), optional | catalog read and cart checkout only |
@mayarin/sdk/x402/connect | none | connect middleware for the x402 gate |
The split is enforced by types, not by a runtime check: MayarinBrowserConfig has no secretKey field, and the browser commerce module has no create or update. A secret key in browser code does not compile.
The x402 subpath is separate for a different reason: the request-time gate is keyless. Your secret key registers a resource once, through @mayarin/sdk, and never appears on the path a payer’s request takes.
import { createMayarin } from "@mayarin/sdk"; // server
import { createMayarinBrowser } from "@mayarin/sdk/browser"; // browser
import { x402Connect } from "@mayarin/sdk/x402/connect"; // the gateWhat the transport does for you
Every module call goes through one transport, so no module re-implements the table stakes:
- Versioned base URL.
baseUrlis the API origin; the transport appends/v1. Never include it yourself. Mayarin-Versionheader. Pinned per SDK build (MAYARIN_VERSION, currently2026-08-11), the date revision inside/v1. See API versioning.- Bearer auth. The secret or publishable key, whichever the entry point holds.
Idempotency-Keyon every write. Auto-generated withcrypto.randomUUID()unless you pass your own — see idempotency.- One error type. Any failure — an API error body, a non-JSON response, a dead socket — arrives as
MayarinApiError.
const intent = await mayarin.payment.getIntent("pi_01KZ…");
// GET https://api.mayarin.xyz/v1/payment-intents/pi_01KZ…
// Authorization: Bearer sk_…
// Mayarin-Version: 2026-08-11Idempotency
Writes carry a generated key by default, which protects a retry inside a single call but not a retry across process restarts. When your system already has a stable identifier for the operation, pass it:
const link = await mayarin.commerce.paymentLinks.create(body, {
idempotencyKey: "order-4711",
});Reusing a key with the same request returns the original result. Reusing it with different parameters is a conflict.
Runtimes
The SDK depends on fetch, crypto.randomUUID, and WebCrypto only, so it runs unchanged on Node 20+, Bun, and Cloudflare Workers. There is no Node-specific crypto import — webhook verification uses crypto.subtle.
Both fetch and the idempotency-key generator are injectable, which is also how the SDK’s own tests run without a server:
const mayarin = createMayarin({
baseUrl: "https://api.mayarin.xyz",
secretKey,
fetch: instrumentedFetch,
generateIdempotencyKey: () => nextKeyFromYourQueue(),
});A working example
apps/demo is a complete storefront built on this SDK, small enough to read in one sitting:
| File | Shows |
|---|---|
seed.ts | Upserting a catalog by SKU with commerce.products |
server/index.ts | The thin server: catalog reads, link minting, a verified webhook endpoint, payment status |
server/worker.ts | The same integration on Cloudflare Workers |
The browser never sees the secret key — the build fails if it reaches the bundle.
Where to go next
- Server client — configuration, environment handling, the raw transport escape hatch.
- Browser client — the publishable surface and hosted checkout hand-off.
- Payments module — intents, confirmation, status, contract calldata.
- Commerce module — products, carts, payment links, invoices, subscriptions.
- Webhook helpers — verify a delivery, then re-query the API.
- x402 gate — sell one of your own endpoints per request, to an agent.
- SDK reference — generated type tables.