yapily-sdk
v1.0.0
Published
Production-grade TypeScript SDK for the Yapily Open Banking API v12 — payments, accounts, VRP, hosted pages, enrichment and more.
Maintainers
Readme
yapily-sdk
Production-grade TypeScript SDK for the Yapily Open Banking API v12. Connect to 2,000+ banks across the UK and Europe.
Features
- Strict TypeScript —
strict: true, zeroany, full type inference - Dual ESM + CJS — works in Node.js 18+, browsers, Deno, Cloudflare Workers
- Middleware pipeline — request/response interceptors
- Retry & backoff — exponential retry on 429/5xx
- Async pagination —
stream(),pages(),listAll(),PageIterator - Fibonacci consent polling — per Yapily docs
- Webhook verification — HMAC-SHA256 via Web Crypto API
- Two error formats —
ApiError+EnhancedApiError+ 8 predicates - 48 error codes as typed constants
- Zero production dependencies (only
zodfor optional runtime validation)
Installation
npm install yapily-sdk
# or
pnpm add yapily-sdkQuick start
import { YapilyClient, idempotencyKey } from "yapily-sdk";
const client = new YapilyClient({
appKey: process.env.YAPILY_APP_KEY!,
appSecret: process.env.YAPILY_APP_SECRET!,
});
// List supported banks
const institutions = await client.institutions.list();
// Start account authorisation (redirect flow)
const auth = await client.authorisations.createAccount({
institutionId: "monzo",
applicationUserId: "your-user-id",
callback: "https://yourapp.com/callback",
featureScopeList: ["ACCOUNTS", "TRANSACTIONS"],
oneTimeToken: true,
});
// Redirect user to: auth.authorisationUrl
// Exchange the one-time token from your callback
const consent = await client.consents.exchangeOneTimeToken(token);
// Read accounts
const accounts = await client.accounts.list(consent.id);
// Stream transactions (memory-efficient)
for await (const txn of client.transactions.stream(consent.id, accountId)) {
console.log(txn.amount, txn.currency, txn.description);
}Payments
Domestic payment
const payment = await client.payments.create(consentToken, {
type: "DOMESTIC_PAYMENT",
paymentIdempotencyId: "inv-001",
idempotencyKey: idempotencyKey("inv-001"),
amount: 100.00,
currency: "GBP",
payee: {
name: "Jane Smith",
accountIdentifications: [
{ type: "SORT_CODE", identification: "200000" },
{ type: "ACCOUNT_NUMBER", identification: "55779911" },
],
},
reference: "Invoice-001",
});International payment
await client.payments.create(consentToken, {
type: "INTERNATIONAL_PAYMENT",
amount: 500.00,
currency: "GBP",
payee: {
name: "Maria Schmidt",
address: { country: "DE" },
accountIdentifications: [
{ type: "IBAN", identification: "DE89370400440532013000" },
{ type: "BIC", identification: "COBADEFFXXX" },
],
},
internationalPayment: {
currencyOfTransfer: "EUR",
chargeBearer: "DEBT",
priority: "NORMAL",
purpose: "GDDS",
},
});Standing order (periodic)
await client.payments.create(consentToken, {
type: "DOMESTIC_PERIODIC_PAYMENT",
paymentDateTime: "2025-01-01T09:00:00Z",
amount: 1200.00,
currency: "GBP",
payee: { name: "Landlord", accountIdentifications: [...] },
periodicPayment: {
frequency: "MONTHLY",
executionDay: 1,
intervalMonth: 1,
numberOfPayments: 12,
},
});Variable Recurring Payments (VRP)
// Authorise once
const vrp = await client.vrp.createSweepingAuthorisation({
institutionId: "monzo",
applicationUserId: "user-id",
callback: "https://yourapp.com/vrp-callback",
controlParameters: {
currency: "GBP",
maximumIndividualAmount: 500.00,
periodicLimits: [{
maximumAmount: 2000.00,
currency: "GBP",
periodType: "Month",
periodAlignment: "Calendar",
}],
},
});
// Redirect to: vrp.authorisationUrl
// Sweep repeatedly — no re-auth needed
const payment = await client.vrp.createPayment(consentToken, vrp.id, {
amount: 250.00,
currency: "GBP",
recipient: { name: "Savings", accountIdentifications: [...] },
reference: "Monthly sweep",
});Pagination
// All pages at once
const all = await client.transactions.listAll(consentToken, accountId);
// Lazy streaming (memory-efficient, stops early)
for await (const txn of client.transactions.stream(consentToken, accountId)) {
if (txn.amount > 1000) break; // stops fetching immediately
}
// Page by page
for await (const page of client.transactions.pages(consentToken, accountId, { limit: 50 })) {
processBatch(page);
}
// Generic PageIterator
import { PageIterator } from "yapily-sdk";
const iter = new PageIterator({
pageSize: 25,
fetch: (offset, limit) =>
client.transactions.list(consentToken, accountId, { offset, limit }),
});
const first100 = await iter.take(100);
const all = await iter.collectAll();Consent polling
import { waitForAuthorisation } from "yapily-sdk";
// After redirecting the user and receiving no callback:
const consent = await waitForAuthorisation(client.consents, consentId);
// Uses Fibonacci back-off: 1s, 1s, 2s, 3s, 5s, 8s, 13s, 21s, 34s
switch (consent.status) {
case "AUTHORIZED": proceed(consent); break;
case "REJECTED": showError(); break;
case "FAILED": showError(); break;
}Error handling
import {
ApiError, EnhancedApiError,
isNotFound, isRateLimited, isVopRejected,
isInsufficientFunds, isVrpLimitExceeded, isRetryable,
} from "yapily-sdk";
try {
const accounts = await client.accounts.list(consentToken);
} catch (err) {
if (isNotFound(err)) return handleNotFound();
if (isRateLimited(err)) return scheduleRetry();
if (isVopRejected(err)) return handleVopFailure();
if (isInsufficientFunds(err)) return handleNoFunds();
if (isVrpLimitExceeded(err)) return handleVrpLimit();
if (isRetryable(err)) return retry();
if (err instanceof ApiError) {
console.error(`${err.statusCode} ${err.code}: ${err.message}`);
console.error(`trace: ${err.traceId}, source: ${err.source}`);
}
if (err instanceof EnhancedApiError) {
for (const issue of err.issues) {
console.error(`[${issue.code}] ${issue.type}: ${issue.message}`);
}
}
}Webhook verification
import { verifyWebhookSignature } from "yapily-sdk";
// Express / Next.js / Hono / any framework
app.post("/webhooks/yapily", async (req, res) => {
const rawBody = req.rawBody; // raw Buffer / string
const sig = req.headers["x-yapily-signature"];
const secret = process.env.YAPILY_WEBHOOK_SECRET!;
const result = await verifyWebhookSignature(rawBody, secret, sig);
if (!result.ok) {
return res.status(401).json({ error: result.reason });
}
// Safe to process
processWebhookEvent(req.body);
res.status(200).end();
});Middleware / Interceptors
// Logging middleware
client.use(async (req, next) => {
const start = Date.now();
console.log(`→ ${req.method} ${req.url}`);
const res = await next(req);
console.log(`← ${res.status} (${Date.now() - start}ms)`);
return res;
});
// Add custom headers to every request
client.use(async (req, next) => {
req.headers["x-correlation-id"] = generateCorrelationId();
return next(req);
});
// Structured logging hook (alternative to middleware)
const client = new YapilyClient({
appKey: "...",
appSecret: "...",
logger: (event) => {
if (event.level === "error") logger.error(event);
else logger.info(event);
},
});Cancellation
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000); // cancel after 5s
const accounts = await client.accounts.list(consentToken, {
signal: controller.signal,
});Service reference
| Namespace | Description |
|---|---|
| client.institutions | List and inspect supported banks |
| client.accounts | Account detail and balances |
| client.transactions | History — list, listAll, stream, pages |
| client.payments | All 6 payment types |
| client.bulkPayments | Batch payment initiation |
| client.consents | Consent lifecycle — exchange, extend, delete |
| client.authorisations | 14 authorisation flows |
| client.financialData | Balances, DD, statements, identity |
| client.users | PSU management |
| client.vrp | Variable Recurring Payments |
| client.notifications | Event subscriptions |
| client.dataPlus | Transaction enrichment |
| client.hostedPages | Yapily-hosted UIs |
| client.constraints | Per-institution constraints |
| client.applicationManagement | App + sub-app management |
| client.webhooksManagement | Webhook registration |
| client.beneficiaries | App + user beneficiaries (VoP) |
| client.validate | Account ownership verification |
License
MIT
