npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

moneyhub-sdk

v1.0.0

Published

Production-grade TypeScript SDK for the Moneyhub Open Finance API: account aggregation (AIS), payments (PIS), OpenID Connect authentication, and webhook verification.

Downloads

29

Readme

moneyhub-sdk

Production-grade TypeScript SDK for the Moneyhub Open Finance API: account aggregation (AIS), payment initiation (PIS), OpenID Connect authentication, and webhook verification — all in one zero-dependency package.

npm license types


Contents


Features

  • Complete API coverage — 35 domain services mirroring every resource group in the Moneyhub V3 API
  • Full OIDC support — PAR, authorisation URL building, code exchange, client credentials, refresh, private_key_jwt assertions, id_token + webhook JWT verification
  • Retry / back-off — configurable exponential back-off with jitter on 5xx responses, Retry-After-aware 429 handling, per-request AbortSignal propagation
  • Structured errorsMoneyhubError with discriminated reason, status, code, retryAfter, isRetryable, and narrow type guards (isApiError, isRateLimitedError, …)
  • Zero runtime dependencies — the only required APIs are fetch (Node 18+, browsers, Deno, Bun, CF Workers) and Web Crypto SubtleCrypto (same runtime coverage)
  • Dual ESM + CJS build — full exports map, tree-shakeable, works in every module system
  • TypeScript-first — strict types throughout, source maps, declaration maps

Requirements

  • Node.js 18.17.0 or later (for global fetch and crypto.subtle)
  • TypeScript 5.0+ (if using from TypeScript source)

Browsers, Deno, Bun, and Cloudflare Workers are supported wherever fetch and crypto.subtle are available.


Installation

npm install moneyhub-sdk
# or
pnpm add moneyhub-sdk
# or
yarn add moneyhub-sdk

Quick start

import { MoneyhubClient, createConfig, MoneyhubClaims, aisScopes } from "moneyhub-sdk";

// 1. Build a config once (typically at app startup).
const config = createConfig("your-client-id", "production", {
  privateKey: await crypto.subtle.importKey(
    "pkcs8",
    pkcs8DerBytes,           // your RSA private key as a DER/PKCS#8 ArrayBuffer
    { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
    false,
    ["sign"],
  ),
  keyId: "your-key-id",       // the kid registered with Moneyhub
  redirectUri: "https://yourapp.example.com/callback",
});

// 2. Instantiate the client (cheap — no network calls at construction time).
const moneyhub = new MoneyhubClient(config);

// 3. Start an AIS (account aggregation) flow with PAR.
const { url } = await moneyhub.auth.pushAuthorisationRequest({
  scope: aisScopes(),                         // "openid accounts:read transactions:read"
  claims: new MoneyhubClaims().putSub(""),    // empty sub = register a new Moneyhub user
  state: crypto.randomUUID(),
});

// Redirect the user to `url` …

// 4. After redirect back, exchange the code.
const tokens = await moneyhub.auth.exchangeCode(searchParams.get("code")!);

// 5. Use the access token to read accounts.
const accounts = await moneyhub.accounts.list(tokens.accessToken);
console.log(accounts);

Configuration

import { createConfig, importPrivateKeyPem } from "moneyhub-sdk";

const config = createConfig(
  "your-client-id",     // required
  "production",         // "production" | "sandbox"
  {
    // Key material — required for private_key_jwt (default authMethod).
    privateKey: await importPrivateKeyPem(fs.readFileSync("private.pem", "utf-8")),
    keyId: "key-1",

    // Optional OAuth redirect target.
    redirectUri: "https://yourapp.example.com/callback",

    // Override base URLs (e.g. for a dedicated sandbox host).
    identityUrl: "https://identity.moneyhub.co.uk",
    apiUrl: "https://api.moneyhub.co.uk/v3.0",

    // Use HTTP Basic instead of private_key_jwt (sandbox / early dev only).
    // authMethod: "client_secret_basic",
    // clientSecret: "your-client-secret",

    // Fetch/timeout tuning.
    fetch: customFetch,     // inject a custom fetch for logging or proxies
    timeoutMs: 30_000,      // per-request timeout (default 60 s)
    maxRetries: 3,          // retries on 429/5xx (default 2)
    maxRetryAfterMs: 15_000, // cap on Retry-After driven sleeps (default 30 s)
  },
);

createConfig throws a MoneyhubError with reason: "config_error" immediately if the resulting configuration is invalid (missing key, unknown environment, etc.).


Authentication

Pushed Authorisation Requests (PAR)

PAR keeps the claims payload off the browser URL bar entirely. Prefer it in production:

import { MoneyhubClaims, aisOfflineScopes } from "moneyhub-sdk";

const { url, requestUri, expiresIn } = await moneyhub.auth.pushAuthorisationRequest({
  scope: aisOfflineScopes(),
  claims: new MoneyhubClaims()
    .putSub("existing-user-id")     // omit or pass "" for new users
    .putCategoryType("personal"),
  state: sessionState,
});

// Redirect the user's browser to `url`.
res.redirect(url);

Building an authorisation URL inline

For simpler use cases (no large claims payload):

const url = await moneyhub.auth.buildAuthorisationUrl({
  scope: "openid accounts:read",
  state: sessionState,
});

Exchanging a code for tokens

const tokens = await moneyhub.auth.exchangeCode(
  req.query.code,
  "https://yourapp.example.com/callback",  // override redirectUri for this call
);
// tokens: { accessToken, idToken?, refreshToken?, tokenType, expiresIn?, scope? }

Client Credentials tokens

For server-to-server calls on behalf of a known user, or for application-level endpoints (discovery, catalog):

// Scoped to a specific user — used to read their data.
const userToken = await moneyhub.auth.tokenForUser(userId, "accounts:read transactions:read");

// Unscoped application token — used for catalog/discovery endpoints.
const appToken = await moneyhub.auth.tokenForUser();

Refresh tokens

const fresh = await moneyhub.auth.refreshToken(tokens.refreshToken);

Verifying id_tokens

const claims = await moneyhub.auth.verifyIdToken(tokens.idToken);
// claims["sub"] is the user's Moneyhub userId.
const userId = claims["sub"] as string;

MoneyhubClaims builder

import { MoneyhubClaims } from "moneyhub-sdk";

const claims = new MoneyhubClaims()
  .putSub("user-123")
  .putCategoryType("personal")
  .putConnectionId("conn-456")          // for re-auth of a specific connection
  .putPayment({ amount: 5000, ... });   // for PIS consent

// Serialise for embedding in an authorisation request.
const json = claims.toString();

Account aggregation (AIS)

// List all connected accounts.
const accounts = await moneyhub.accounts.list(token, { accountType: "cash:current" });

// Fetch a single account.
const account = await moneyhub.accounts.get(token, accountId);

// Historical balance snapshots.
const balances = await moneyhub.accounts.balances(token, accountId, {
  fromDate: "2024-01-01",
  toDate:   "2024-06-30",
});

// List transactions.
const transactions = await moneyhub.transactions.list(token, {
  accountId,
  fromDate: "2024-01-01",
  limit: 100,
});

// Correct a transaction's category.
await moneyhub.transactions.updateCategory(token, transactionId, categoryId);

// Split a transaction.
await moneyhub.transactions.split(token, transactionId, [
  { amount: 3500, category: "groceries" },
  { amount: 1500, category: "household" },
]);

// Spending analysis (aggregated stats).
const analysis = await moneyhub.spendingAnalysis.get(token, {
  fromDate: "2024-01-01",
  toDate:   "2024-01-31",
  categoryType: "personal",
});

Payment initiation (PIS)

Single Immediate Payment

import { MoneyhubClaims, paymentScopes } from "moneyhub-sdk";

// 1. Create a payee.
const payee = await moneyhub.payees.create(appToken, {
  name: "Acme Ltd",
  accountIdentifications: [{ type: "SortCodeAccountNumber", identification: "60161300012345" }],
});

// 2. Start the payment auth flow.
const { url } = await moneyhub.auth.pushAuthorisationRequest({
  scope: paymentScopes(),
  claims: new MoneyhubClaims()
    .putSub(userId)
    .putPayment({
      payeeId: payee.id,
      amount: 10000,               // in pence
      currency: "GBP",
      payeeType: "registered",
      reference: "INV-001",
    }),
});
res.redirect(url);

// 3. After redirect, exchange the code and poll for status.
const tokens = await moneyhub.auth.exchangeCode(code);
const claims = await moneyhub.auth.verifyIdToken(tokens.idToken!);
const paymentId = (claims["mh:payment"] as { paymentId: string }).paymentId;

// 4. Poll.
const payment = await moneyhub.payments.status(tokens.accessToken, paymentId);

Variable Recurring Payments (VRP)

// Trigger a sweep against an established VRP consent.
const sweep = await moneyhub.recurringPayments.sweep(vrpToken, consentId, {
  amount: 25000,
  reference: "Monthly savings sweep",
});

// Check available funds without triggering a payment.
const available = await moneyhub.recurringPayments.confirmFunds(vrpToken, consentId, {
  amount: 25000,
});

Standing orders

// View a standing order created via PIS.
const so = await moneyhub.standingOrders.get(token, standingOrderId);

// Cancel it.
await moneyhub.standingOrders.cancel(token, standingOrderId);

Pay Links

// Create a shareable, hosted payment link.
const link = await moneyhub.payLinks.create(appToken, {
  amount: 5000,
  currency: "GBP",
  reference: "Invoice 42",
  payeeId: payee.id,
});

// Share link.url with the payer.
console.log(link.url);

Webhooks

import { WebhooksService } from "moneyhub-sdk";

// Share the auth service's JWKS cache when using MoneyhubClient — this
// is done automatically; shown here only for standalone usage.
const verifier = new WebhooksService(
  config.identityUrl,
  fetch,
  moneyhub.auth.jwksCache,   // optional; if omitted a fresh cache is created
);

// In your webhook endpoint handler (Express example):
app.post("/webhook", express.raw({ type: "*/*" }), async (req, res) => {
  // Acknowledge immediately — Moneyhub has a 5-second response timeout.
  res.sendStatus(200);

  try {
    const event = await verifier.parse(req.body);  // Buffer or string
    // event.id         — e.g. "newTransactions", "paymentCompleted"
    // event.userId     — Moneyhub user id
    // event.payload    — event-specific fields
    // event.raw        — full decoded payload

    switch (event.id) {
      case "newTransactions":
        await handleNewTransactions(event.userId!, event.payload);
        break;
      case "paymentCompleted":
        await handlePaymentCompleted(event.payload);
        break;
    }
  } catch (err) {
    console.error("webhook verification failed", err);
  }
});

WebhooksService.parse automatically detects whether the incoming body is a plain JSON object or a compact JWS (signed JWT delivery), and verifies the signature against Moneyhub's JWKS for the latter.


Error handling

Every function that can fail throws a MoneyhubError. Use the reason discriminant and type guards to branch:

import {
  isMoneyhubError,
  isRateLimitedError,
  isApiError,
  isNetworkError,
} from "moneyhub-sdk";

try {
  const accounts = await moneyhub.accounts.list(token);
} catch (err) {
  if (isRateLimitedError(err)) {
    // err.retryAfter is the Retry-After header value in seconds (when present).
    await sleep((err.retryAfter ?? 5) * 1000);
    return retry();
  }

  if (isNetworkError(err)) {
    // Transient connectivity failure — safe to retry with back-off.
    return retry();
  }

  if (isApiError(err)) {
    // err.status  — HTTP status code (4xx or 5xx)
    // err.code    — Moneyhub error code string, e.g. "INVALID_REQUEST"
    // err.body    — raw decoded response body
    console.error(`API error ${err.status} (${err.code ?? "unknown"})`);
    return;
  }

  throw err;
}

err.isRetryable is true for rate_limited, network_error, and api_error with a 5xx status — matching the conditions the built-in retry logic already handles automatically.

err.toJSON() returns a plain, serialisable object safe to pass to an error tracker or structured log.

Error reasons

| reason | When thrown | |---------------------|-----------------------------------------------------------------------| | config_error | createConfig called with invalid or missing parameters | | validation_error | A required parameter was missing or out of range | | network_error | The fetch call threw (DNS failure, TCP reset, timeout, etc.) | | api_error | Moneyhub returned a non-2xx response (4xx other than 429, or 5xx) | | rate_limited | Moneyhub returned 429 Too Many Requests | | decode_error | The response or webhook body could not be decoded as expected JSON | | jwt_error | A JWS/JWT could not be verified (bad signature, expired, missing kid) |


Domain services reference

| Property on MoneyhubClient | Service class | Summary | |------------------------------------|----------------------------------------|-----------------------------------------------| | auth | AuthService | OIDC flows, PAR, tokens, id_token verification | | accounts | AccountsService | Accounts, balances, AIS standing orders | | affordability | AffordabilityService | Affordability / income-verification reports | | authRequests | AuthRequestsService | Hosted auth request URL generation | | bankIcons | BankIconsService | Bank/institution icon images | | beneficiaries | BeneficiariesService | Payees detected from transaction history | | categories | CategoriesService | Category taxonomy, categorisation-as-a-service | | connections | ConnectionsService | Bank connection lifecycle + catalog | | consentHistory | ConsentHistoryService | Consent audit trail | | counterparties | CounterpartiesService | Merchants/payees behind transactions | | discovery | DiscoveryService | OIDC discovery document | | globalCounterparties | GlobalCounterpartiesService | Global merchant reference database | | holdings | HoldingsService | Investment holdings, ISIN matching | | notificationThresholds | NotificationThresholdsService | Balance alert thresholds (VRP triggers) | | payees | PayeesService | Creditor accounts for PIS | | payFile | PayFileService | Bulk/batch payment files | | payLinks | PayLinksService | Shareable hosted payment links | | payments | PaymentsService | Single Immediate Payment status + refunds | | projects | ProjectsService | User-defined account groupings | | recurringPayments | RecurringPaymentsService | VRP consents + sweeps | | regularTransactions | RegularTransactionsService | Detected recurring transaction series | | rentalRecords | RentalRecordsService | Rent payment credit reporting | | resellerCheck | ResellerCheckService | Reseller/partner validation | | savingsGoals | SavingsGoalsService | Savings progress goals | | scimUsers | ScimUsersService | SCIM user identity (widget provisioning) | | spendingAnalysis | SpendingAnalysisService | Aggregated category spending stats | | spendingGoals | SpendingGoalsService | Budget/spending goals | | standardFinancialStatements | StandardFinancialStatementsService | SFS reports (lending/collections) | | standingOrders | StandingOrdersService | PIS standing orders | | statements | StatementsService | AIS account statements | | tax | TaxService | SA105 / property income data | | transactions | TransactionsService | Transactions, splits, file attachments | | users | UsersService | User CRUD, user-scoped connections | | webhooks | WebhooksService | Webhook parsing + JWS verification |


Advanced usage

Injecting a custom fetch

import { createConfig } from "moneyhub-sdk";

// Example: wrap fetch with structured logging.
const loggingFetch: typeof fetch = async (input, init) => {
  const start = Date.now();
  const res = await fetch(input, init);
  console.log(`${String(init?.method ?? "GET")} ${String(input)} → ${res.status} (${Date.now() - start}ms)`);
  return res;
};

const config = createConfig("client-id", "production", {
  privateKey: key,
  keyId: "k1",
  fetch: loggingFetch,
});

Using individual services without MoneyhubClient

All domain services can be constructed directly from a MoneyhubConfig:

import { AccountsService, createConfig } from "moneyhub-sdk";

const config = createConfig(...);
const accounts = new AccountsService(config);
const list = await accounts.list(token);

Key import helper

import { importPrivateKeyPem } from "moneyhub-sdk";
import { readFileSync } from "node:fs";

const key = await importPrivateKeyPem(readFileSync("private-key.pem", "utf-8"));

Development

# Install dependencies
npm install

# Type-check
npm run typecheck

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Build (ESM + CJS + .d.ts)
npm run build

# Lint
npm run lint

# Format
npm run format

Project structure

src/
  core/
    config.ts          — createConfig, MoneyhubConfig
    errors.ts          — MoneyhubError, type guards
    http-client.ts     — HttpClient, retry/backoff, MoneyhubResponse
    base-service.ts    — BaseService, shared helpers
  auth/
    auth-service.ts    — AuthService (OIDC flows)
    claims.ts          — MoneyhubClaims builder
    id-token.ts        — JWS/id_token verification
    jwks.ts            — JwksCache, JWK type
    jwt.ts             — RS256 signing, importPrivateKeyPem
    scopes.ts          — Scopes constants, scope helpers
  domains/
    accounts.ts        — AccountsService
    … (35 domain services)
  client.ts            — MoneyhubClient facade
  index.ts             — Public API surface
test/
  sdk.test.ts          — 44 tests covering errors, config, HTTP, auth, webhooks

License

MIT © 2025 Kanishka