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

@spare-technologies/spare-api

v1.0.0

Published

Official TypeScript / Node.js SDK for the Spare Open Banking API

Readme

SpareApi TypeScript / Node.js SDK

The official TypeScript SDK for the SpareApi Open Banking API.

Table of Contents

Installation

npm install @spare-technologies/spare-api
# or
yarn add @spare-technologies/spare-api
# or
pnpm add @spare-technologies/spare-api

Requires Node.js 18 or later.

Versioning

The SDK follows Semantic Versioning. Breaking changes are indicated by a major version bump.

All users are strongly recommended to use a recent version of the library, as older versions may not contain support for new endpoints and fields.

Getting Started

Configuration

Create a Configuration instance with your API credentials. The SDK automatically handles token exchange and management:

import { SpareApiClient, Configuration } from "@spare-technologies/spare-api";

// Create configuration with your credentials
const config = new Configuration({
  appId: process.env.SPARE_APP_ID!,
  apiKey: process.env.SPARE_API_KEY!,
  tenant: "UAE", // or "KSA"
  environment: "sandbox", // or "production"
});

// Initialize the client (Configuration handles everything: auth, tenant, baseUrl)
const client = new SpareApiClient({ authProvider: config });

// SDK automatically exchanges credentials for a bearer token on first request
// Token is cached and refreshed as needed—no manual token handling required
// X-Tenant header is auto-injected on every request
const { data: providers } = await client.providers.list({ countryCode: "AE" });

Configuration Options

| Option | Required | Type | Default | Description | |--------|----------|------|---------|-------------| | appId | ✅ | string | - | App identifier from Spare dashboard | | apiKey | ✅ | string | - | API key from Spare dashboard | | tenant | ✅ | "UAE" | "KSA" | - | Target tenant | | environment | ❌ | "sandbox" | "production" | "sandbox" | API environment | | baseUrl | ❌ | string | - | Custom base URL (overrides environment) |

Multi-Environment Support

// Sandbox (default)
const sandboxConfig = new Configuration({
  appId: "app_sandbox_123",
  apiKey: "sk_sandbox_456",
  tenant: "UAE",
  environment: "sandbox", // Hits https://api.sandbox.tryspare.ae
});

// Production
const prodConfig = new Configuration({
  appId: "app_prod_789",
  apiKey: "sk_prod_101112",
  tenant: "UAE",
  environment: "production", // Hits https://api.tryspare.ae
});

// Custom Base URL (e.g., local development)
const localConfig = new Configuration({
  appId: "app_local_123",
  apiKey: "sk_local_456",
  tenant: "UAE",
  baseUrl: "http://localhost:4000", // Custom URL takes precedence
});

Token Management

Configuration handles token exchange and refresh automatically:

  1. First Request: Credentials are exchanged for an access token via POST /auth/api-keys/sessions
  2. Caching: Token is cached in memory with a 30-minute TTL
  3. Auto-Refresh: When token expires, a new token is automatically fetched via POST /auth/api-keys/sessions/refresh
  4. Concurrent Safety: Multiple concurrent requests use a single token (no duplicate exchanges)

You don't need to manage tokens manually — Configuration handles it all.

Error Handling

All non-2xx responses throw a SpareApiError. Inspect statusCode and body to handle specific error types:

import { SpareApiError } from "@spare-technologies/spare-api";

try {
  const consent = await client.paymentConsents.get("consent-id");
} catch (err) {
  if (err instanceof SpareApiError) {
    console.error(err.statusCode); // HTTP status — e.g. 404
    console.error(err.message);    // human-readable message
    console.error(err.body);       // raw response body
  }
  throw err;
}

Examples

For more examples see the API reference documentation.

List providers

Retrieve the open-banking providers available in a given country:

const { data: providers } = await client.providers.list({
  countryCode: "AE",
});

for (const provider of providers) {
  console.log(provider.id, provider.name);
}

Create a payment request

Payment requests require a request signature. Use client.crypto.signPayload to produce the detached JWS and pass it as x-signature:

const body = {
  type: "SingleInstantPayment",
  purpose: "ACM",
  creditorType: "MERCHANT",
  creditorReference: "INV-10042",
  creditorAccount: {
    identification: "AE070331234567890123456",
    name: "Acme Corp",
    schemeName: "IBAN",
  },
  instructions: {
    amount: { amount: "250.00", currency: "AED" },
  },
};

const xSignature = await client.crypto.signPayload(process.env.SPARE_PRIVATE_KEY_PEM!, body);

const { data: paymentRequest } = await client.paymentRequests.create({
  ...body,
  "x-signature": xSignature,
});

console.log("Payment request:", paymentRequest.id);
// Redirect the user to this URL to authorise the payment at their bank
console.log("Authorise at:", paymentRequest.redirectUrl);

Check payment consent status

After the user authorises at the bank (via redirectUrl), consent is created server-side. Poll or sync status using paymentConsents.list, paymentConsents.get, or paymentConsents.sync:

// List all consents (paginated)
const { data: consents } = await client.paymentConsents.list({ page: 1, perPage: 10 });
for (const c of consents) {
  console.log(c.id, c.status);
}

// Get a specific consent by ID
const { data: consent } = await client.paymentConsents.get("consent-id");
console.log("Consent status:", consent.status);

// Force a sync from the bank to get the latest status
const { data: synced } = await client.paymentConsents.sync({
  paymentRequestId: paymentRequest.id,
});
console.log("Synced status:", synced.status);

Get a payment

const { data: payment } = await client.payments.get(paymentId);

console.log("Status:", payment.status);

Register a bank account

const { data: account } = await client.bankAccounts.create({
  consentId: consent.id, // consent obtained from paymentConsents.get("consent-id")
  accountNumber: "1234567890",
  bankCode: "ADCB",
});

console.log("Account registered:", account.id);

Schedule a mandate

Scheduling a mandate also requires a request signature:

const mandateBody = {
  mandateId: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  amount: 500,
  executionDate: "2025-06-01",
};

const xSignature = await client.crypto.signPayload(process.env.SPARE_PRIVATE_KEY_PEM!, mandateBody);

const { data: transaction } = await client.mandates.schedule({
  ...mandateBody,
  "x-signature": xSignature,
});

console.log("Mandate scheduled:", transaction.id);

Spare Link (hosted payments)

Spare Link is a hosted open-banking payment experience. Your merchant backend uses this SDK for two server-side steps:

  1. Mint a short-lived link_token via link.createPaymentToken
  2. Exchange the one-time exchange_code from the client Link SDK via link.exchange

Pass the link_token to the hosted UI SDK on your client (@sparefinancial/link-web, Flutter spare_link, React Native @sparefinancial/link-react-native). Those client packages handle the bank consent UI — this API SDK covers token mint and exchange only.

Mint a link token

const paymentRequest = {
  type: "SingleInstantPayment",
  purpose: "ACM",
  creditorType: "MERCHANT",
  creditorReference: "INV10042",
  creditorAccount: {
    identification: "AE070331234567890123456",
    name: "Acme Corp",
    schemeName: "IBAN",
  },
  instructions: {
    amount: { amount: "250.00", currency: "AED" },
  },
};

const xSignature = await client.crypto.signPayload(
  process.env.SPARE_PRIVATE_KEY_PEM!,
  paymentRequest
);

const { data: linkSession } = await client.link.createPaymentToken({
  user_ref: "user-123",
  provider_code: "ENBD", // optional — omit to show bank selection in hosted UI
  request: paymentRequest,
  "x-signature": xSignature,
});

// Pass linkSession.link_token to your frontend Link SDK
console.log("Link token:", linkSession.link_token);
console.log("Payment request:", linkSession.request?.id);

Exchange an authorization code

After the end-user completes bank authorisation, your client Link SDK fires onSuccess with an exchange_code. Send that code to your backend and exchange it for durable identifiers:

const { data: result } = await client.link.exchange({
  exchange_code: exchangeCodeFromClient,
});

console.log("Payment ID:", result.paymentId);
console.log("Consent ID:", result.consentId);
console.log("Status:", result.paymentStatus);

Request signing (x-signature)

Certain endpoints — paymentRequests.create, link.createPaymentToken, and mandates.schedule — require a detached JWS signature, passed via the x-signature field. For link.createPaymentToken, sign only the nested request object (same payload as paymentRequests.create), not the full Link body. The SDK ships a built-in CryptoHelper (available as client.crypto) so you don't need any external crypto library.

Prerequisites

  1. Generate an EC P-256 key pair and register the public key in your Spare dashboard.
  2. Store the private key securely (environment variable, secret manager).

Signing a payment request

import { SpareApiClient, Configuration, CryptoHelper } from "@spare-technologies/spare-api";

const config = new Configuration({
  appId: process.env.SPARE_APP_ID!,
  apiKey: process.env.SPARE_API_KEY!,
  tenant: "UAE",
  environment: "sandbox",
});
const client = new SpareApiClient({ authProvider: config });

const privateKeyPem = process.env.SPARE_PRIVATE_KEY_PEM!; // PKCS8 PEM

const body = {
  type: "SingleInstantPayment",
  purpose: "ACM",
  creditorType: "MERCHANT",
  creditorReference: "INV-10042",
  creditorAccount: {
    identification: "AE070331234567890123456",
    name: "Acme Corp",
    schemeName: "IBAN",
  },
  instructions: {
    amount: { amount: "250.00", currency: "AED" },
  },
};

// 1. Sign the body — client.crypto is a lazily-initialised CryptoHelper instance
const xSignature = await client.crypto.signPayload(privateKeyPem, body);

// 2. Pass the signature alongside the body
const { data: paymentRequest } = await client.paymentRequests.create({
  ...body,
  "x-signature": xSignature,
});

console.log("Payment request created:", paymentRequest.id);

Signing a mandate

const mandateBody = {
  mandateId: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  amount: 500,
  executionDate: "2025-06-01",
};

const xSignature = await client.crypto.signPayload(privateKeyPem, mandateBody);

const { data: transaction } = await client.mandates.schedule({
  ...mandateBody,
  "x-signature": xSignature,
});

console.log("Mandate scheduled:", transaction.id);

Using CryptoHelper standalone

CryptoHelper can also be used independently — useful for testing or pre-computing signatures:

import { CryptoHelper } from "@spare-technologies/spare-api";

const crypto = new CryptoHelper();

// Canonical JSON (must match what you pass to the API)
const canonical = crypto.serializePayload(body);
console.log("Canonical payload:", canonical);

// ES256 detached JWS
const jws = await crypto.signPayload(privateKeyPem, body);
console.log("x-signature:", jws); // eyJhbGciOiJFUzI1NiJ9...<sig>

Key format: The private key must be a PKCS8 PEM string starting with -----BEGIN PRIVATE KEY-----. Generate one with:

openssl ecparam -genkey -name prime256v1 -noout | openssl pkcs8 -topk8 -nocrypt -out private.pem
openssl ec -in private.pem -pubout -out public.pem   # register public.pem in your dashboard

License

This SDK is distributed under the MIT License. See the bundled LICENSE file.