An unofficial, production-grade TypeScript client for the Treasury Prime Ledger API — bank accounts, ACH, wires, book transfers, FedNow, cards, KYC/KYB account opening, check deposit, webhooks, sandbox simulations, and more.
Zero runtime dependencies. Dual ESM/CJS with bundled type declarations. Node.js 18.17+.
npm install treasuryprime-sdkimport { Client, TreasuryPrimeError } from "treasuryprime-sdk";
const client = new Client(
process.env.TREASURY_PRIME_KEY_ID!,
process.env.TREASURY_PRIME_KEY_VALUE!,
{ environment: "sandbox" }, // default; use "production" when you're ready
);
try {
const account = await client.accounts.get("acct_0112233445566");
console.log(`${account.name}: ${account.available_balance} available`);
} catch (err) {
if (err instanceof TreasuryPrimeError) {
console.error(`treasury prime error (${err.type}, HTTP ${err.statusCode}): ${err.message}`);
}
throw err;
}A lightweight ports-and-adapters (hexagonal) layout, scaled to what an API client actually needs — there's no business logic to isolate, so the boundary that matters is the network:
- Port —
Fetch(the shape of the globalfetchfunction) is the one seam between this library and the network. Swap in an instrumented, rate-limited, or test-double implementation viaClientOptions.fetch. - Adapter — the internal
Transportclass is the one concrete adapter: it turns method calls into authenticated, retried, JSON HTTP requests and turns responses into typed values orTreasuryPrimeError. - Domain — every resource (
Account,Ach,Wire,Card, ...) is a plain TypeScript interface mirroring the wire format. Each resource's*Serviceclass (AccountService,AchService, ...) is a thin, explicit adapter mappinglist/get/create/update/deleteontoTransportcalls — a repository pattern, with the repetitive plumbing shared via aBaseService<T>and each resource's own file keeping its documented, typed public methods.
| Domain | Client property | Notes |
|---|---|---|
| Accounts | accounts |
+ lock/unlock, setInterestRate, averageBalances, dailyBalances, transactions, pendingTransactions, generateStatement/fetchStatement, tax documents |
| Account applications | accountApplications |
|
| Account locks | accountLocks |
read-only history |
| Account number reservations | accountNumberReservations |
|
| Account products | accountProducts |
read-only |
| ACH | ach |
+ cancel |
| Additional person applications | additionalPersonApplications |
|
| Book transfers | books |
|
| Businesses | businesses |
|
| Business applications | businessApplications |
|
| Cards | cards |
+ activate/suspend/terminate/reissue; get supports show_pan/show_cvv via options.query |
| Card auth loop endpoints | cardAuthLoopEndpoints |
|
| Card events | cardEvents |
read-only |
| Card products | cardProducts |
|
| Checks | checks |
+ cancel, stopPayment |
| Check deposits | checkDeposits |
|
| Counterparties | counterparties |
|
| Deposits (account funding) | deposits |
|
| Deposit sweeps | depositSweeps |
|
| Digital wallet tokens | digitalWalletTokens |
+ provisionWithApplePay, provisionWithGooglePay |
| Documents | documents |
|
| FedNow | fednow |
+ routingNumberStatus |
| Files | files |
raw upload/download/get |
| Green Dot | greendot |
+ locations |
| Incoming ACH | incomingAch |
+ returnTransfer |
| Incoming wires | incomingWires |
read-only |
| Invoice account numbers | invoiceAccountNumbers |
|
| KYC | kyc |
|
| KYC products | kycProducts |
read-only |
| Manual holds | manualHolds |
+ release |
| Network transfers | networkTransfers |
|
| People | people |
|
| Person applications | personApplications |
|
| Reserve accounts | reserveAccounts |
|
| Routing numbers | routingNumbers |
read-only lookup |
| Statement configs | statementConfigs |
|
| Transactions | transactions |
|
| Webhooks | webhooks |
+ enable/disable |
| Wires | wires |
+ cancel |
| Marqeta | marqeta |
jsAccessToken, uxToolkitAccessToken (for Marqeta-processor programs) |
| Sandbox simulations | simulation |
create (general) + achStatus, wireStatus, checkDepositStatus, fedNowReceive, greendotComplete, cardAuthRequest |
Every create accepts an idempotencyKey option:
import { generateIdempotencyKey } from "treasuryprime-sdk";
const key = generateIdempotencyKey();
const ach = await client.ach.create(
{
account_id: "acct_1",
counterparty_id: "cp_1",
amount: "104.20",
direction: "debit",
},
{ idempotencyKey: key },
);Generate one key per business operation and persist it alongside that operation — reuse it across retries of the same operation rather than minting a fresh one per HTTP call.
list() returns a Page<T>, which implements AsyncIterable<T> — the
idiomatic way to consume it is for await...of, which fetches pages lazily
and stops as soon as you break:
const page = await client.ach.list({ query: { status: "pending" } });
for await (const transfer of page) {
if (transfer.amount === "0.00") break; // no further pages are fetched
console.log(transfer.id, transfer.status);
}
// or, eagerly collect everything:
const all = await page.all();
// or iterate page-by-page instead of item-by-item:
for await (const p of page.pages()) {
console.log(`got a page of ${p.data.length} items`);
}import { validateWebhookSignature, WebhookEvent } from "treasuryprime-sdk";
app.post("/webhooks/treasury-prime", async (req, res) => {
if (!validateWebhookSignature(req.headers["authorization"], whUser, whSecret)) {
return res.sendStatus(401);
}
const event = WebhookEvent.parse(req.rawBody);
if (event.event.startsWith("ach.")) {
const ach = await event.fetch<Ach>(client);
// handle ach
}
res.sendStatus(200);
});Treasury Prime intentionally sends a thin payload and doesn't guarantee
delivery order — always re-fetch via WebhookEvent#fetch rather than
trusting embedded state.
await client.simulation.achStatus(achId, "settled");Fails against a "production" client — Treasury Prime enforces this itself.
Every network-touching call rejects with a TreasuryPrimeError — always
instanceof TreasuryPrimeError, with a type ("api_error",
"network_error", "timeout", "validation_error", "decode_error"), and
for API errors, statusCode and the decoded response body.
Endpoints not covered by a typed resource method (or a brand-new one Treasury Prime just shipped) are reachable via the same authenticated, retried transport everything else uses:
const result = await client.request<Record<string, unknown>>("GET", "/some/new/endpoint");const client = new Client(keyId, keyValue, {
environment: "production",
maxRetries: 3,
retryBaseDelayMs: 500,
maxRetryDelayMs: 10_000,
receiveTimeoutMs: 30_000,
fetch: myInstrumentedFetch, // matches the global fetch signature
onRequest: ({ method, url }) => logger.debug(`${method} ${url}`),
});Every method also accepts a per-call options object (idempotencyKey,
headers, query, signal) as its last argument.
Treasury Prime doesn't publish a single machine-readable schema. The field
set here was extracted from a companion, hand-maintained Elixir client and
cross-checked against Treasury Prime's docs where available. Entity and
params interfaces mirror the wire format directly (snake_case, matching
Treasury Prime's JSON) rather than being translated to camelCase, so what
you see in a debugger or curl response matches what you see in your editor.
Deeply nested, bank-specific, or rarely-documented sub-objects
(physical_address, bankdata, card_controls, fx_quote, and similar)
are intentionally typed as Record<string, unknown> rather than
guessed-at nested interfaces, so this client never silently drops a field
Treasury Prime adds or a particular bank partner requires. Verify
field-level behavior against Treasury Prime's docs before relying on it
for anything money-moving.
npm run check # typecheck, lint, format check, test
npm run build # dual ESM/CJS + .d.ts via tsupMIT — see LICENSE.