@cdx-forge/di-typescript-sdk
v0.1.0-beta.1
Published
TypeScript server SDK for the Candescent Digital Insight API
Readme
@candescent/di-typescript-sdk
Server-side TypeScript/Node.js SDK for the Candescent Digital Insight API. Covers all 99 API operations across 12 service areas with full type safety, automatic OAuth token management, and built-in retry logic.
Requirements
- Node.js 20+
- ESM only — use
importsyntax.require()is not supported (see Troubleshooting).
Installation
npm install @candescent/di-typescript-sdkyarn add @candescent/di-typescript-sdkpnpm add @candescent/di-typescript-sdkQuick Start
import { CandescentClient, Environment } from "@candescent/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();Authentication
Obtain your Client ID, Client Secret, and Institution ID from the Candescent Developer Console. For detailed steps, see the Quick Start guide.
Option 1 — OAuth 2.0 Client Credentials (recommended)
import { CandescentClient, Environment } from "@candescent/di-typescript-sdk";
const client = new CandescentClient({
clientId: "your-client-id",
clientSecret: "your-client-secret",
institutionId: "your-institution-id",
environment: Environment.Production,
});The SDK obtains and caches OAuth tokens automatically, refreshes them before expiry, and injects the correct Authorization header on every request. You never handle tokens directly.
Option 2 — 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)import { CandescentClient } from "@candescent/di-typescript-sdk";
const client = CandescentClient.fromEnv();Option 3 — Static Bearer Token
import { CandescentClient } from "@candescent/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 — user username |
| CANDESCENT_PASSWORD | No | Password grant — user password |
*Provide either CANDESCENT_BEARER_TOKEN or CANDESCENT_CLIENT_ID + CANDESCENT_CLIENT_SECRET.
Note: Node.js does not automatically load
.envfiles. Either runsource .envbefore your script, or use a library likedotenvto load them at runtime.
Usage
The client is organized by API service area. Each property on client maps to one group of operations.
Accounts
// List accounts for a user
const accounts = await client.accounts.list({ hostUserId: "user-12345" });
// Get a specific account
const account = await client.accounts.get({ accountId: "acc-abc123" });
// List transactions for an account
const txns = await client.accounts.listTransactions({
accountId: "acc-abc123",
hostUserId: "user-12345",
});Customer Management
import type { RegisterCustomerRequest } from "@candescent/di-typescript-sdk";
// Register a new customer
const response = await client.customerManagement.register({
diFiid: "05523",
body: {
fICustomer: {
id: { value: "0", type: "GUID" },
fiId: { value: "05523" },
memberNumber: "123456789",
person: {
personName: { firstName: "Jane", lastName: "Smith" },
contactInfo: { emailAddress: "[email protected]" },
},
userType: "PRIMARY",
},
},
});
const customerId = response.fICustomer?.id?.value;
// Look up customer information
const info = await client.customerManagement.getInformation({
customerId,
});Authentication
// Create an OAuth token
const token = await client.authentication.createToken({
grantType: "client_credentials",
});
// Revoke a token on logout
await client.authentication.revokeToken({
token: token.accessToken,
});Business Banking
// Get business entitlements
const entitlements = await client.businessBanking.getBusinessEntitlements({
businessId: "biz-456",
});
// Look up business details by ID
const details = await client.businessBanking.getBusinessDetails({
searchType: "BUSINESS_ID",
searchValue: "biz-456",
includeTins: true,
includeUsers: true,
});Standalone Functions (Serverless / Lambda)
For environments where managing a client lifecycle is inconvenient:
import { listAccounts, getAccount, listTransactions } from "@candescent/di-typescript-sdk/operations";
// Reads credentials from process.env.CANDESCENT_*
const accounts = await listAccounts({ hostUserId: "user-12345" });
const account = await getAccount({ accountId: "acc-abc123" });Error Handling
All API errors are thrown as typed exceptions. Catch the base ApiError or any specific subclass:
import {
ApiError,
AuthenticationError,
NotFoundError,
RateLimitError,
BadRequestError,
} from "@candescent/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; // re-throw unexpected errors
}
}Error Hierarchy
| HTTP Status | Exception Class |
|-------------|----------------|
| Any non-2xx | ApiError (base) |
| 400 | BadRequestError |
| 401 | AuthenticationError |
| 403 | PermissionDeniedError |
| 404 | NotFoundError |
| 409 | ConflictError |
| 422 | UnprocessableEntityError |
| 429 | RateLimitError |
| 5xx | InternalServerError |
Pagination
Endpoints that return lists support PageIterator — an async iterable that handles fetching subsequent pages automatically:
import { PageIterator } from "@candescent/di-typescript-sdk";
// Iterate all accounts across all pages (25 per page by default)
for await (const account of new PageIterator(
(req) => client.accounts.list({ hostUserId: "user-12345", ...req }),
{ page: 0, size: 50 },
)) {
console.log(account.accountId);
}Or collect all results into an array:
import { PageIterator } from "@candescent/di-typescript-sdk";
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 on transient failures with exponential backoff:
| Retried Status Codes | Max Retries | Initial Delay | Max Delay | |----------------------|-------------|---------------|-----------| | 408, 429, 500, 502, 503, 504 | 2 (3 total attempts) | 500ms | 30s |
RateLimitError is thrown only after all retries are exhausted. The retryAfter property contains the value from the Retry-After response header.
Framework Integration
Express / Fastify
import express from "express";
import { CandescentClient } from "@candescent/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
import { listAccounts } from "@candescent/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
const client = CandescentClient.fromEnv();
// On shutdown — revokes cached tokens
await client.close();Configuration Reference
import { CandescentClient, Environment } from "@candescent/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 ---
bearerToken: "...", // Pre-obtained JWT
// --- Optional ---
environment: Environment.Production, // Environment.Stage (default) | Environment.Production
baseUrl: "https://custom.api.host", // Override base URL (advanced)
});Examples
The examples/ directory contains 21 runnable scripts covering all API operations:
| Example File | What It Covers |
|---|---|
| accounts.ts | List accounts, retrieve account details, banking images |
| transactions.ts | Transaction history, filtering |
| authentication.ts | OAuth token lifecycle |
| authentication-lifecycle.ts | Full token create / revoke flow |
| customer-management.ts | Contact methods, password reset, unlock |
| register-and-lookup.ts | Customer registration + 3-way lookup |
| disclosures.ts | Institution and user disclosures |
| estatements.ts | E-statement delivery preferences |
| alert-configuration.ts | Alert templates and types |
| alert-preferences.ts | User and institution alert preferences |
| alert-delivery.ts | Alert history and content |
| notification-channels.ts | Subscription management |
| business-registration.ts | Business banking registration |
| business-entitlements.ts | Business and user entitlements |
| business-payments.ts | ACH and wire payments |
| customer-campaigns.ts | Experience groups and campaigns |
| money-movement.ts | Recipients and transfers |
| mx-service.ts | MX integration — widget, users, logs |
| pagination.ts | PageIterator across all pagination patterns |
| error-handling.ts | Full error hierarchy showcase |
| user-status.ts | User authentication status |
To run an example:
# 1. Copy and fill in your credentials
cp .env.example .env
# 2. Install dependencies
npm install
# 3. Run any example
node --experimental-strip-types examples/typescript/accounts.tsTroubleshooting
CommonJS require() not working
This package is ESM-only. Your project must use "type": "module" in package.json or use .mjs file extensions. require('@candescent/di-typescript-sdk') will throw ERR_REQUIRE_ESM.
Cannot find module '@candescent/di-typescript-sdk'
Run npm install @candescent/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, set
CANDESCENT_ENVIRONMENT=stage
"Parameters hostUserId and loginId are mutually exclusive"
Pass only one user identifier per request — either hostUserId or loginId, not both.
"Module not found" when running examples
Run npm install in the repo root or examples/ directory to install dependencies.
Versioning
This SDK follows semantic versioning independently from the Candescent API spec version. The API specification version (currently 1.4.0) and the SDK package version (starting at 1.0.0) are tracked separately. The release notes document which API spec version each SDK release was generated from.
Support
- API Reference: docs.candescent.com
- Issues: GitHub Issues
