mayarin
SDKTypeScript

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/sdk

Three entry points

ImportKeySurface
@mayarin/sdksecret (sk_…)commerce and payment in full
@mayarin/sdk/browserpublishable (pk_…), optionalcatalog read and cart checkout only
@mayarin/sdk/x402/connectnoneconnect 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 gate

What the transport does for you

Every module call goes through one transport, so no module re-implements the table stakes:

  • Versioned base URL. baseUrl is the API origin; the transport appends /v1. Never include it yourself.
  • Mayarin-Version header. Pinned per SDK build (MAYARIN_VERSION, currently 2026-08-11), the date revision inside /v1. See API versioning.
  • Bearer auth. The secret or publishable key, whichever the entry point holds.
  • Idempotency-Key on every write. Auto-generated with crypto.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-11

Idempotency

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:

FileShows
seed.tsUpserting a catalog by SKU with commerce.products
server/index.tsThe thin server: catalog reads, link minting, a verified webhook endpoint, payment status
server/worker.tsThe 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.