solaris-client
v1.0.1
Published
Production-grade TypeScript SDK for the Solaris Embedded Finance API. Covers Onboarding, KYC, Digital Banking, SEPA, Cards, Lending, and Webhooks.
Downloads
201
Maintainers
Readme
solaris-client
Production-grade TypeScript SDK for the Solaris Embedded Finance API.
Covers the full API surface — Onboarding, KYC, Digital Banking, SEPA, Cards, Lending, and Webhooks — with idiomatic TypeScript: full type safety, async generators for lazy pagination, automatic retries, OAuth2 token management, and Web Crypto webhook verification.
Features
- ✅ Full API coverage — 259+ endpoints across Persons, Businesses, KYC, Accounts, SEPA, Cards, Loans, Trade Finance, and more
- 🔐 OAuth2 token management — Automatic client-credentials token fetch and caching with proactive refresh
- 🔁 Automatic retries — Exponential back-off with jitter on 429/5xx responses
- 🌊 Lazy pagination — Async generators (
for await) for memory-efficient iteration over large result sets - 🪝 Webhook verification — HMAC-SHA256 signature verification using the Web Crypto API (Node 18+, Deno, Bun, browsers)
- 🔑 Idempotency keys — Auto-generated on every POST/PUT/PATCH, overridable per-request
- 💪 Type-safe — Complete TypeScript types for every request param, response body, and error
- 🌐 Universal — Works in Node.js 18+, Deno, Bun, and modern browsers (via custom
fetch) - 🪶 Zero runtime dependencies — Uses only the Web platform APIs
Installation
npm install solaris-client
# or
yarn add solaris-client
# or
pnpm add solaris-clientRequirements: Node.js ≥ 18 (for fetch, crypto.subtle, and FormData).
Quick Start
import { SolarisClient } from "solaris-client";
const client = new SolarisClient({
clientId: process.env.SOLARIS_CLIENT_ID!,
clientSecret: process.env.SOLARIS_CLIENT_SECRET!,
environment: "sandbox", // or "production"
});
// ── Onboard a person ──────────────────────────────────────────────────────────
const person = await client.persons.create({
first_name: "Jane",
last_name: "Doe",
email: "[email protected]",
birth_date: "1990-01-15",
nationality: "DE",
address: {
line_1: "Unter den Linden 1",
postal_code: "10117",
city: "Berlin",
country: "DE",
},
});
// ── Verify mobile number ──────────────────────────────────────────────────────
await client.persons.createMobileNumber(person.id, "+491701234567");
await client.persons.authorizeMobileNumber(person.id); // sends SMS
await client.persons.confirmMobileNumber(person.id, "123456");
// ── Start KYC identification ──────────────────────────────────────────────────
const identification = await client.kyc.createPersonIdentification(person.id, {
method: "idnow",
});
// Redirect customer to: identification.url
// ── Check account balance ─────────────────────────────────────────────────────
const accounts = await client.accounts.listPersonAccounts(person.id);
const account = accounts[0]!;
const balance = await client.accounts.getBalance(account.id);
console.log(`Balance: ${balance.balance.amount} ${balance.balance.currency}`);
// ── Initiate a SEPA transfer (triggers SCA/2FA) ───────────────────────────────
const transfer = await client.sepa.createPersonCreditTransfer(
person.id,
account.id,
{
recipient_iban: "DE89370400440532013000",
recipient_name: "Max Mustermann",
amount: 10_000, // cents = €100.00
currency: "EUR",
reference: "Invoice #123",
},
);
// ── Complete SCA via SMS OTP ──────────────────────────────────────────────────
const changeRequestId = (transfer as { change_request_id: string }).change_request_id;
await client.changeRequests.authorizeWithSms(changeRequestId);
// Customer enters OTP from SMS:
await client.changeRequests.confirmWithOtp(changeRequestId, "483920");Configuration
const client = new SolarisClient({
clientId: "your_client_id", // required
clientSecret: "your_secret", // required
environment: "sandbox", // "sandbox" | "production" (default: "sandbox")
timeout: 30_000, // request timeout in ms (default: 30_000)
maxRetries: 3, // retry count on 429/5xx (default: 3)
baseUrl: "http://localhost:4040", // override base URL (testing)
fetch: customFetch, // custom fetch implementation
});Error Handling
All methods throw SolarisError on failure. Use instanceof to catch API errors:
import { SolarisClient, SolarisError } from "solaris-client";
try {
const person = await client.persons.get("cper_unknown");
} catch (err) {
if (err instanceof SolarisError) {
console.log(err.code); // "not_found"
console.log(err.status); // 404
console.log(err.message); // "Person not found"
console.log(err.requestId); // "req_abc123" — useful for support tickets
console.log(err.details); // raw error objects from Solaris API
// Check if SCA is required (202 response body)
if (err.isChangeRequestRequired()) {
const crId = err.changeRequestId!;
await client.changeRequests.authorizeWithSms(crId);
}
}
}Error codes
| Code | HTTP | Description |
|------|------|-------------|
| unauthorized | 401 | Invalid or missing credentials |
| forbidden | 403 | Insufficient permissions |
| not_found | 404 | Resource does not exist |
| conflict | 409 | State conflict (e.g. duplicate) |
| unprocessable_entity | 422 | Validation failure |
| rate_limited | 429 | Too many requests (auto-retried) |
| internal_server_error | 500 | Solaris server error (auto-retried) |
| timeout | — | Request timed out |
| network_error | — | TCP/DNS-level failure |
Pagination
List methods return a single page. For large datasets use the async generator:
// Single page
const page = await client.persons.list({ per_page: 50 });
console.log(page.data); // Person[]
console.log(page.meta.total); // total count
// Lazy page-by-page iteration (recommended — never loads all data into memory)
for await (const page of client.persons.listAll()) {
for (const person of page.data) {
await process(person);
}
}
// Or use the raw HTTP client for any endpoint:
for await (const page of client.http.paginate("/v1/accounts/cacc_123/bookings")) {
console.log(page.data);
}
// Collect all into an array (use only for small datasets)
const all = await client.http.listAll("/v1/persons");Webhooks
Express / Node.js
import express from "express";
import { verifyAndParseWebhook, SolarisError } from "solaris-client";
const app = express();
// Must use raw body — do NOT run JSON middleware before this route
app.post(
"/webhooks/solaris",
express.raw({ type: "*/*" }),
async (req, res) => {
const sig = req.headers["solaris-webhook-signature"] as string;
let event;
try {
event = await verifyAndParseWebhook(
req.body as Buffer,
sig,
process.env.SOLARIS_WEBHOOK_SECRET!,
);
} catch (err) {
if (err instanceof SolarisError) {
res.status(401).json({ error: err.message });
return;
}
res.status(400).json({ error: "Invalid payload" });
return;
}
// Always return 200 — Solaris retries on non-2xx
res.sendStatus(200);
// Handle events asynchronously
switch (event.event_type) {
case "BOOKING":
await handleBooking(event.payload);
break;
case "IDENTIFICATION":
await handleIdentification(event.payload);
break;
case "SCA_CHALLENGE":
await handleScaChallenge(event.payload);
break;
// Always add a default for forward compatibility
default:
console.log(`Unhandled event: ${event.event_type}`);
}
},
);Manual verification
import { verifyWebhookSignature, parseWebhookEvent } from "solaris-client";
// Step 1: verify signature (throws SolarisError on failure)
await verifyWebhookSignature(rawBody, signature, secret);
// Step 2: parse the event
const event = parseWebhookEvent(JSON.parse(rawBody.toString()));
console.log(event.event_type); // "BOOKING"
console.log(event.delivery_id); // "del_abc123"
console.log(event.payload); // full decoded body
console.log(event.received_at); // DateConsumer Loan Flow
// 1. Create application
const app = await client.lending.createConsumerLoanApplication(personId, {
amount: 10_000_00, // €10,000 in cents
currency: "EUR",
term_months: 36,
purpose: "CONSUMER_GOODS",
});
// 2. Wait for CONSUMER_LOAN_APPLICATION webhook with status "OFFERED"
// 3. Download SECCI — legally required before signing (EU Consumer Credit Directive)
const secciPdf = await client.lending.getSECCI(personId, app.id, offerId);
// Present secciPdf to customer
// 4. Get final contract
const contractPdf = await client.lending.getContract(personId, app.id, offerId);
// 5. Create loan after customer signs
const loan = await client.lending.createLoan(personId, app.id, {
offer_id: offerId,
signing_id: signingId,
});Change Request (SCA/2FA) Flow
Many write operations (transfers, person updates, trusted IBANs) require 2FA. The API returns a 202 with change_request_id:
// SMS OTP flow
await client.changeRequests.authorizeWithSms(changeRequestId);
const completed = await client.changeRequests.confirmWithOtp(changeRequestId, otp);
// Device signing flow (Solaris mobile SDK)
const { challenge } = await client.changeRequests.getDeviceChallenge(changeRequestId);
// Pass challenge to mobile SDK → get signedToken
const completed = await client.changeRequests.confirmWithDevice(changeRequestId, signedToken);
// Poll until complete (useful in tests; prefer webhooks in production)
const result = await client.changeRequests.pollUntilComplete(changeRequestId, {
intervalMs: 2_000,
maxAttempts: 15,
});Sandbox Testing
// Simulate a card authorization (triggers SCA_CHALLENGE webhook)
await client.cards.sandboxSimulateAuthorization(cardId, {
amount: 5_000,
currency: "EUR",
});
// Robot-based KYC identification
await client.kyc.sandboxIdentifyWithRobot("AUTOTEST-APPROVED");
// Simulate expired ID document
await client.persons.simulateIdDocumentExpiry(personId);
// Set cash operation status manually
await client.transactions.sandboxSetCashOperationStatus(accountId, opId, "PAID");API Reference
| Namespace | Description |
|-----------|-------------|
| client.persons | Person CRUD, mobile numbers, tax IDs, documents, language settings |
| client.businesses | Business CRUD, legal reps, beneficial owners, WIdNr |
| client.kyc | Identification sessions, signings, screener hits |
| client.accounts | Accounts, balances, bookings, statements, savings, IBANs |
| client.sepa | SEPA Credit Transfers (standard + instant) and Direct Debits |
| client.transactions | Cash ops, top-ups, remittances, reference payouts, prepaid |
| client.cards | Card issuance, lifecycle, 3DS, tokenization, credit cards |
| client.lending | Consumer loans, overdraft, Splitpay, snapshots, trade finance |
| client.changeRequests | SCA/2FA completion via SMS OTP or device signing |
| client.http | Raw HTTP client for endpoints not yet in the SDK |
Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Type check
npm run typecheck
# Build
npm run build
# Lint
npm run lintLicense
MIT — see LICENSE.
