@cdx-forge/di-typescript-sdk
v1.2.0
Published
TypeScript server SDK for the Candescent Digital Insight API
Readme
Candescent Digital Insight — TypeScript SDK
Server-side TypeScript SDK for the Candescent Digital Insight API. Covers all API operations with full type safety, automatic OAuth token management, and built-in retry logic.
| Package | Version |
|---------|---------|
| This package (@cdx-forge/di-typescript-sdk) | 1.2.0 |
| OpenAPI spec (candescent-dev/openapi) | 1.8.0 |
The npm package version and the OpenAPI specification version are tracked separately. See Versioning and CHANGELOG.md.
Overview
This SDK is the recommended way for server-side TypeScript and Node.js applications to integrate with the Candescent Digital Insight platform. It wraps the full REST API surface — accounts, transactions, customer management, business banking, alerts, money movement, notifications, disclosures, e-statements, and MX services — behind a single typed client that handles the protocol complexity so your application does not have to.
Key things the SDK takes care of on your behalf:
- OAuth token management — acquires, caches, and refreshes tokens automatically across two token endpoint families (V1 and V2), and injects the correct
Authorizationheader on every request. - Typed models — every request parameter and response shape is generated from the OpenAPI specification, giving you autocomplete and compile-time safety.
- Error hierarchy — HTTP error codes are mapped to typed exceptions (
NotFoundError,RateLimitError, etc.) so you can catch exactly what you need. - Pagination — an async-iterable
PageIteratorhandles all four pagination patterns used across the API. - Retry with backoff — transient failures (408, 429, 5xx) are retried automatically with exponential backoff.
The SDK is ESM-only and requires Node.js 20+. For browser or edge runtimes, call the REST API directly using the Candescent API reference.
Documentation
See the Candescent API reference for full endpoint documentation, and the examples folder for runnable code across every service area.
Requirements
| Requirement | Details |
|-------------|---------|
| Node.js | 20+ |
| Module system | ESM only — use import syntax. require() will throw ERR_REQUIRE_ESM (see Troubleshooting) |
Installation
npm install @cdx-forge/di-typescript-sdk
# or
yarn add @cdx-forge/di-typescript-sdk
# or
pnpm add @cdx-forge/di-typescript-sdkUsage
The client is initialized once and reused across your application. It accepts credentials directly or reads them from environment variables. All service areas are available as properties on the client instance, and every method is fully typed — parameters, responses, and errors.
Configure the client with your credentials from the Candescent Developer Console:
import { CandescentClient, Environment } from "@cdx-forge/di-typescript-sdk";
const client = new CandescentClient({
clientId: process.env.CANDESCENT_CLIENT_ID!,
clientSecret: process.env.CANDESCENT_CLIENT_SECRET!,
institutionId: process.env.CANDESCENT_INSTITUTION_ID!,
environment: Environment.Stage, // or Environment.Production
});
const accounts = await client.accounts.list({ hostUserId: "user-12345" });
console.log(`Found ${accounts.accounts?.length ?? 0} accounts`);
await client.close();Or load all credentials from environment variables:
const client = CandescentClient.fromEnv();The client is organized by service area. Each property maps to a group of fully typed API operations:
| Property | Service area |
|----------|-------------|
| client.accounts | Account listing, account details, transactions |
| client.customerManagement | Customer registration, lookup, profile management |
| client.authentication | Token creation and revocation |
| client.businessBanking | Business entitlements, business details |
| client.alerts | Alert configuration, delivery, preferences |
| client.moneyMovement | Payments and transfers |
| client.notifications | Notification channels and subscriptions |
| client.disclosures | Disclosure management |
| client.estatements | E-statement access |
| client.mxPlatform | MX Platform proxy APIs |
| client.realTime | MX Real Time proxy APIs |
| client.reporting | MX Reporting proxy APIs |
| client.sso | MX SSO proxy APIs |
Standalone functions
For serverless environments (e.g. AWS Lambda) where managing a persistent client is impractical, import individual operation functions directly:
import { listAccounts, getAccount } from "@cdx-forge/di-typescript-sdk/operations";
// Reads credentials from process.env.CANDESCENT_*
const accounts = await listAccounts({ hostUserId: "user-12345" });
const account = await getAccount({ accountId: "acc-abc123" });Authentication
The package needs to be configured with your Client ID, Client Secret, and Institution ID, available in the Candescent Developer Console.
The SDK obtains and caches OAuth tokens automatically, refreshes them before expiry, and injects the correct Authorization header on every request — you never manage tokens directly.
OAuth V1 and V2
The Candescent API uses two distinct OAuth token endpoint families that exist for historical reasons. Newer endpoints use the V2 standard JSON flow; a subset of legacy endpoints require the older V1 flow (which may return XML). The SDK handles both transparently:
| | V1 (Legacy) | V2 (Current) |
|---|---|---|
| Token endpoint | POST /v1/oauth/token | POST /oauth2/v1/token |
| Token lifetime | ~30 minutes | ~1 hour |
| Response format | XML or JSON | JSON |
| Typical operations | Customer registration, account listing via /bankingservices/, notification channels | Accounts, alerts, MX, money movement, disclosures, e-statements |
The SDK inspects the request URL path at call time and selects the correct token provider automatically — V1 for paths containing /bankingservices/, /registration/, /destinations/, /subscriptions/, or /send-event/, and V2 for everything else. Tokens are cached and reused for their lifetime, then refreshed proactively before expiry. XML token responses from V1 endpoints are parsed internally; you always receive typed TypeScript objects regardless of what the wire format was.
No configuration is needed beyond your client credentials. If you already have a pre-obtained JWT, you can pass it directly and skip the OAuth flow entirely (see Using a static bearer token below).
Using environment variables
export CANDESCENT_CLIENT_ID=your-client-id
export CANDESCENT_CLIENT_SECRET=your-client-secret
export CANDESCENT_INSTITUTION_ID=your-institution-id
export CANDESCENT_ENVIRONMENT=production # or stage (default)const client = CandescentClient.fromEnv();Scripts do not load
.envfiles automatically. Runsource .envfirst, or usedotenv.
Using a static bearer token
If you have a pre-obtained JWT, pass it directly — the OAuth flow is skipped:
import { CandescentClient } from "@cdx-forge/di-typescript-sdk";
const client = new CandescentClient({
bearerToken: "your-pre-obtained-jwt",
institutionId: "your-institution-id",
});Environment variables reference
| Variable | Required | Description |
|----------|----------|-------------|
| CANDESCENT_INSTITUTION_ID | Always | Your institution identifier |
| CANDESCENT_CLIENT_ID | Yes* | OAuth 2.0 client ID |
| CANDESCENT_CLIENT_SECRET | Yes* | OAuth 2.0 client secret |
| CANDESCENT_BEARER_TOKEN | Yes* | Pre-obtained JWT (skips OAuth flow) |
| CANDESCENT_ENVIRONMENT | No | stage (default) or production |
| CANDESCENT_USERNAME | No | Password grant username |
| CANDESCENT_PASSWORD | No | Password grant password |
*Provide either CANDESCENT_BEARER_TOKEN or CANDESCENT_CLIENT_ID + CANDESCENT_CLIENT_SECRET.
Error handling
Every non-2xx response is surfaced as a typed exception rather than a generic Error, so you can handle specific failure modes (not found, rate limited, unauthenticated) without parsing status codes yourself. Catch the base ApiError for a catch-all, or catch specific subclasses for targeted handling:
import {
ApiError,
AuthenticationError,
NotFoundError,
RateLimitError,
} from "@cdx-forge/di-typescript-sdk";
try {
const account = await client.accounts.get({ accountId: "acc-not-found" });
} catch (error) {
if (error instanceof NotFoundError) {
console.error("Account not found:", error.message);
} else if (error instanceof RateLimitError) {
console.warn(`Rate limited. Retry after ${error.retryAfter}s`);
} else if (error instanceof AuthenticationError) {
console.error("Check your credentials");
} else if (error instanceof ApiError) {
console.error(`API error ${error.statusCode}:`, error.message);
} else {
throw error;
}
}| HTTP status | Exception |
|-------------|-----------|
| Any non-2xx | ApiError (base) |
| 400 | BadRequestError |
| 401 | AuthenticationError |
| 403 | PermissionDeniedError |
| 404 | NotFoundError |
| 409 | ConflictError |
| 422 | UnprocessableEntityError |
| 429 | RateLimitError |
| 5xx | InternalServerError |
Pagination
The Candescent API uses several different pagination styles across its endpoints (offset-based, cursor-based). PageIterator abstracts over all of them with a single async iterable interface — you iterate over items and the SDK fetches the next page when needed, without you tracking page numbers or tokens:
import { PageIterator } from "@cdx-forge/di-typescript-sdk";
for await (const account of new PageIterator(
(req) => client.accounts.list({ hostUserId: "user-12345", ...req }),
{ page: 0, size: 50 },
)) {
console.log(account.accountId);
}To collect all results into an array:
const allAccounts = [];
for await (const account of new PageIterator(
(req) => client.accounts.list({ hostUserId: "user-12345", ...req }),
)) {
allAccounts.push(account);
}Retry behavior
The SDK automatically retries transient failures with exponential backoff:
| Retried on | Max retries | Initial delay | Max delay | |------------|-------------|---------------|-----------| | 408, 429, 500, 502, 503, 504 | 2 (3 total attempts) | 500 ms | 30 s |
RateLimitError is thrown only after all retries are exhausted. Check error.retryAfter for the value from the Retry-After response header.
Framework integration
The client is stateful (it holds a token cache) and safe to instantiate once at application startup and share across requests. For serverless functions where a persistent instance is impractical, use the standalone functions export instead (see Standalone functions above, and the AWS Lambda example below).
Express / Fastify
import express from "express";
import { CandescentClient } from "@cdx-forge/di-typescript-sdk";
const app = express();
const client = CandescentClient.fromEnv();
app.get("/accounts/:userId", async (req, res) => {
const accounts = await client.accounts.list({ hostUserId: req.params.userId });
res.json(accounts);
});AWS Lambda
Use the standalone functions export to avoid managing client lifecycle across invocations:
import { listAccounts } from "@cdx-forge/di-typescript-sdk/operations";
export const handler = async (event: { userId: string }) => {
const accounts = await listAccounts({ hostUserId: event.userId });
return { statusCode: 200, body: JSON.stringify(accounts) };
};Lifecycle management
Call client.close() on shutdown to revoke cached tokens:
await client.close();Hand-written wrapper layer
The SDK is built on top of auto-generated OpenAPI client stubs, but the code your application actually interacts with is entirely hand-written and maintained separately from the generated output. The durable layer lives in src/ and consists of:
| File | What it does |
|------|-------------|
| client.ts | CandescentClient facade — mounts all service areas and runs the middleware pipeline |
| auth.ts | V1 and V2 token providers — token acquisition, caching, pre-expiry refresh, and per-request routing |
| transport.ts | Middleware — injects auth and tracing headers; converts XML responses to JSON before deserialization |
| errors.ts | Maps HTTP status codes to typed ApiError subclasses |
| pagination.ts | PageIterator — async iterable supporting offset and cursor pagination patterns |
| retry.ts | Exponential backoff with jitter for transient failures |
| operations.ts | Standalone functions for serverless environments |
This layer is what survives when the SDK is regenerated from a new OpenAPI spec. If you want to understand exactly how a request flows — from your call site through auth, headers, retries, and back — the source files above are the place to look. You can browse them directly on GitHub.
Configuration
import { CandescentClient, Environment } from "@cdx-forge/di-typescript-sdk";
const client = new CandescentClient({
// --- Required (one of the two auth methods) ---
clientId: "...", // OAuth client ID
clientSecret: "...", // OAuth client secret
institutionId: "...", // Your institution identifier
// OR use a static token instead of client credentials:
bearerToken: "...",
environment: Environment.Production, // Environment.Stage (default)
baseUrl: "https://custom.api.host", // Override base URL (advanced)
});Examples
The examples/typescript/ folder on GitHub contains runnable scripts that show how to consume the SDK end-to-end across every service area. These are the best starting point for understanding real usage patterns — each script is a self-contained illustration of how to initialise the client, make calls, handle responses, and clean up.
Coverage includes: accounts, transactions, authentication and token lifecycle, customer registration and lookup, business banking (registration, entitlements, payments), alerts (configuration, preferences, delivery), money movement, MX integration, notifications, disclosures, e-statements, pagination with PageIterator, and the full error handling hierarchy.
Key characteristics of the example scripts:
- Each script is independent. You can run a single example to explore one service area without needing to run them all.
- Error handling is a separate script (
error-handling.ts) that exercises intentional failures and is excluded from the batch runner by design. - The batch runner (
node scripts/run-all-typescript-examples.mjs) runs all examples in sequence and writes a log, which is useful for verifying your credentials work across the full API surface.
See the Examples README for environment setup, how to run individual scripts, and the full list of example files with what each one covers.
Troubleshooting
CommonJS require() not working
This package is ESM-only. Your project must use "type": "module" in package.json or use .mjs file extensions. require('@cdx-forge/di-typescript-sdk') will throw ERR_REQUIRE_ESM.
Cannot find module '@cdx-forge/di-typescript-sdk'
Run npm install @cdx-forge/di-typescript-sdk and confirm the package is listed in your package.json dependencies.
Authentication errors (401)
- Verify
CANDESCENT_CLIENT_IDandCANDESCENT_CLIENT_SECRETare correct. - Confirm
CANDESCENT_INSTITUTION_IDmatches the credentials issued to you. - For staging environments, ensure
CANDESCENT_ENVIRONMENT=stage.
"Parameters hostUserId and loginId are mutually exclusive"
Pass only one user identifier per request — hostUserId or loginId, not both.
Versioning
This SDK follows semantic versioning independently from the Candescent DI OpenAPI specification. The SDK version and spec version are tracked separately.
| Package | Version |
|---------|---------|
| SDK (@cdx-forge/di-typescript-sdk) | 1.2.0 |
| OpenAPI spec (candescent-dev/openapi) | 1.8.0 |
Each SDK release is pinned to a specific spec version. See CHANGELOG.md for the spec version used in each release.
License
Copyright © Candescent. All rights reserved. The Digital Insight TypeScript SDK is proprietary software.
See LICENSE for the proprietary terms.
Support
- API reference: docs.candescent.com
- Issues: GitHub Issues
- License: Copyright © Candescent. Proprietary — see
LICENSE.
