Appearance
Quickstart — Node.js / TypeScript
Connect your first bank account in sandbox mode with @budgetbakers/partner-sdk. You need a partner account in the partner portal — registration provisions sandbox access instantly, no card required.
1. Install
sh
npm install @budgetbakers/partner-sdkNode ≥ 20; zero runtime dependencies; ESM and CJS. Until the npm package is published, install the tarball from the downloads page instead: npm install ./budgetbakers-partner-sdk-0.1.0.tgz.
2. Create the client
Your API key selects the mode: bb_test_… keys run sandbox (fictional *_xf banks), bb_live_… keys run live. Keys are server-side only — never embed them in client-side code or URLs.
ts
import { BudgetBakers } from '@budgetbakers/partner-sdk';
const bb = new BudgetBakers({ apiKey: '{{BB_API_KEY}}' });
const config = await bb.partner.getConfig();
console.log(config.mode); // "sandbox"3. Create an end-user client
A client is one of your end users — bank connections, accounts and transactions all belong to a client, and the X-Client-Id scoping keeps each user's data isolated. Pass your own user id as externalId and the call becomes an upsert — safe to call on every login, no duplicates, no need to store a BudgetBakers-id mapping on your side:
ts
const client = await bb.clients.create({ externalId: 'user-42' });
const scope = bb.client(client.id); // all user-scoped calls go through this4. Start the hosted connect flow
Create a connect session and send the user to hostedUrl (web redirect, or the mobile Link SDKs — see the iOS/Android quickstarts).
The returnUrl works like an OAuth redirect URI: the portal (Apps → return URLs) holds your allowlist of patterns — one per platform (web URL, iOS universal link, Android app link, custom scheme) — and each session passes the concrete URL for where this user should land. It must match a registered pattern, otherwise the create fails with validation_error.
ts
const session = await scope.connectSessions.create({
returnUrl: 'https://app.example.com/bb-callback',
});
redirectUserTo(session.hostedUrl);The user picks a bank (sandbox: e.g. Sandbox Bank XF), authenticates, and is redirected back to your returnUrl. The redirect is a UX signal — the authoritative result is the session state:
ts
const done = await scope.connectSessions.waitForTerminal(session.sessionId);
if (done.state === 'Completed') {
console.log('connected:', done.connectionId);
}5. Receive webhooks
Configure your webhook URL in the portal (per mode); every delivery is signed. See the webhook verification guide for the full contract.
ts
app.post('/webhooks/bb', express.raw({ type: '*/*' }), (req, res) => {
const result = bb.webhooks.verify(
['{{BB_WEBHOOK_SECRET}}'], // ALL active secrets (two during rotation)
req.header('X-BB-Signature') ?? '',
req.body, // the RAW body bytes
);
if (result !== 'valid') return res.status(401).end();
const event = bb.webhooks.parseEvent(req.body);
if (event.kind === 'event' && event.type === 'ConnectionCreateSuccess') {
// fetch data, update your records …
}
res.status(200).end(); // always 2xx for verified deliveries — unknown types included
});6. Read accounts and transactions
ts
const accounts = await scope.connections.listAccounts(connectionId);
for await (const tx of scope.accounts.transactions(accounts[0].id)) {
// tx.amount is a DecimalString ("1234.56") — money is never a float.
// Only `id` is guaranteed non-null; treat every other field as nullable.
}Error handling
Branch on error.code only — never parse messages (full code reference):
ts
import { PartnerApiError } from '@budgetbakers/partner-sdk';
try {
await scope.connections.refresh(connectionId);
} catch (err) {
if (err instanceof PartnerApiError && err.code === 'refresh_cooldown') {
console.log('retry after', err.nextRefreshPossibleAt);
} else {
throw err;
}
}The SDK already retries 429/5xx with exponential backoff (honoring Retry-After) and auto-generates Idempotency-Key on creates.
Going live
Complete the go-live checklist in the portal (card verification, business check, terms) — production unlocks automatically and you can mint a bb_live_… key. The code above works unchanged; only the key differs.