Skip to content

fintech-sdk/yapily-client

Repository files navigation

yapily-client

npm version TypeScript API v12 CI

Production-grade TypeScript SDK for the Yapily Open Banking API v12. Connect to 2,000+ banks across the UK and Europe.

Features

  • Strict TypeScriptstrict: true, zero any, 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 paginationstream(), pages(), listAll(), PageIterator
  • Fibonacci consent polling — per Yapily docs
  • Webhook verification — HMAC-SHA256 via Web Crypto API
  • Two error formatsApiError + EnhancedApiError + 8 predicates
  • 48 error codes as typed constants
  • Zero production dependencies (only zod for optional runtime validation)

Installation

npm install yapily-client
# or
pnpm add yapily-client

Quick start

import { YapilyClient, idempotencyKey } from "yapily-client";

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-client";

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-client";

// 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-client";

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-client";

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

About

Production-grade TypeScript SDK for the Yapily Open Banking API v12. Connect to 2,000+ banks across the UK and Europe.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors