mayar-node-sdk
v2.0.0
Published
Unofficial-friendly Node.js & Bun SDK for the Mayar Headless API V2 — invoices, payment links, customers, transactions, coupons, webhooks & more.
Maintainers
Readme
mayar-node-sdk
Unofficial, developer-friendly SDK for the Mayar Headless API V2 — works on Node.js (≥ 18) and Bun with zero dependencies (native fetch).
Built from the official docs at docs.mayar.id. Targets API V2 (/hl/v2, /credit/v2, /saas/v2, /software/v2) — API V1 is deprecated on 1 October 2026, so this SDK speaks V2 only. See the V1 → V2 migration guide if you're porting old code.
Install
npm install mayar-node-sdk
# or
bun add mayar-node-sdkQuick start
import { Mayar } from "mayar-node-sdk";
const mayar = new Mayar({
apiKey: process.env.MAYAR_API_KEY, // or MAYAR_API_KEY env is read automatically
environment: "sandbox", // "production" (default) | "sandbox"
});
const invoice = await mayar.invoices.create({
name: "Budi Santoso",
email: "[email protected]",
mobile: "081234567890",
items: [{ quantity: 1, rate: 150000, description: "Konsultasi" }],
});
console.log(invoice.link); // send this to your customerMore in examples/quickstart.mjs and examples/webhook-bun.mjs.
API map
| SDK | Endpoints |
| --- | --- |
| mayar.products | list / listByType / get / transactions / createPaymentLink / updatePaymentLink / sortByType / digital / webinar / event / changeStatus |
| mayar.invoices | list / filterByEmail / get / create / update |
| mayar.payments | list / get / create / update / changeStatus / simulate (sandbox-only) |
| mayar.customers | list / getByEmail / create / updateEmail / createMagicLink |
| mayar.transactions | listPaid / listUnpaid / listDaily / get / getBalance / getStatistics |
| mayar.qrCodes (mayar.qr) | createDynamic / getStatic / getPaymentChannels |
| mayar.coupons (mayar.discounts) | list / create / get / validate / check |
| mayar.installments | list / get / create |
| mayar.membership | tiers / members / memberById / register / updateMember / createInvoice / cancel |
| mayar.credit | balance / spend / addCredit / history / register… / generateImmutableCheckoutLink (/credit/v2) |
| mayar.licenses | verifySaas / activateSaas / deactivateSaas / verifySoftware |
| mayar.reviews | listAllReviews / productReviews / customerReview / stats / create / update / bulkUpdateStatus |
| mayar.webhooks | history / newHistory / register / test / retry |
| mayar.bundling | list / get |
Every list* returns { data, hasMore, nextStartingAfter, total? } (cursor pagination, limit max 50). Every list resource also has listAll() (fetch everything) and iterateAll() (lazy for await…of) so you never handle cursors by hand:
for await (const p of mayar.products.iterateAll({ limit: 50 })) {
console.log(p.id, p.name);
}Configuration
new Mayar({
apiKey: "…", // required (or MAYAR_API_KEY env). Create at web.mayar.id/api-keys
environment: "sandbox", // production (api.mayar.id) | sandbox (api.mayar.io)
baseUrl: "https://…", // override (proxy/mock). Wins over environment.
timeoutMs: 30_000, // per-request timeout
maxRetries: 2, // auto-retry GET on 429 (honours Retry-After) + 5xx
headers: { … }, // extra headers on every request
fetch: customFetch, // testing / proxy / undici agent
});Errors
All failures throw MayarError — the body statusCode is treated as authoritative (some write endpoints return HTTP 200 with a non-200 envelope code):
import { MayarError } from "mayar-node-sdk";
try {
await mayar.coupons.validate({ couponCode: "X", paymentLinkId: "…" });
} catch (err) {
if (err instanceof MayarError) {
if (err.status === 404) console.log("coupon does not exist");
if (err.status === 400) console.log("exists but not applicable:", err.messages);
if (err.isRateLimited) console.log("back off for", err.retryAfterMs, "ms");
}
}Helpers: isAuthError (401) · isNotFound (404) · isConflict (409) · isValidationError (400) · isRateLimited (429) · isRetryable.
Security notes
- Server-side only. The API key (
Authorization: Bearer …) must never ship to browsers — it controls money movement. - Keys are environment-scoped: use the sandbox key only against
api.mayar.io, production key only againstapi.mayar.id. Mismatched pairs return 401. - Read Only vs Read & Write keys: a read-only key can only call
GETendpoints; writes return 401. - POSTs are never auto-retried (the API rejects duplicate creates with 429) — only idempotent GETs retry.
- The SDK never logs your key.
MayarError.rawholds the server body for debugging but never the header. - Webhooks carry your callback token in the
x-callback-tokenrequest header — verify it first withverifyWebhookRequest()(timing-safe, works with FetchHeaders, Expressreq.headers, or Hono getters) and reject mismatches with 401 before parsing. Key it viaMAYAR_WEBHOOK_TOKENenv. Seeexamples/webhook-bun.mjs. - Even for authenticated webhooks, reconcile
amount/statusviatransactions.get(id)before fulfilling orders, and reply 2xx fast.
Rate limits
50 requests/minute per API key. Exceeding it returns 429 + Retry-After — GETs back off and retry automatically; for write-heavy flows, throttle client-side and keep limit ≤ 50.
License
MIT
