@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-apiRequires 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:
- First Request: Credentials are exchanged for an access token via
POST /auth/api-keys/sessions - Caching: Token is cached in memory with a 30-minute TTL
- Auto-Refresh: When token expires, a new token is automatically fetched via
POST /auth/api-keys/sessions/refresh - 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:
- Mint a short-lived
link_tokenvialink.createPaymentToken - Exchange the one-time
exchange_codefrom the client Link SDK vialink.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
- Generate an EC P-256 key pair and register the public key in your Spare dashboard.
- 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.
