Getting Started

Getting Started

Go from zero to retrieving bills in 6 steps with the official billerapi Node SDK. This guide uses the sandbox environment with test data — no real accounts needed.

1

Create a Developer Account

Sign up at the Client Portal. There's no email round-trip to get started — creating your account signs you in immediately and drops you on the dashboard with a working sandbox key already minted.

  1. Go to the Client Portal and click Sign Up
  2. You're signed in right away and land on your developer dashboard
  3. Your sandbox bak_test_ key is ready to copy — start building

Note

Email verification happens later, in-app, and only gates production access — it never blocks signing in or using the sandbox. Verify your email (and complete the go-live checklist) when you're ready to send live traffic.
2

Get Sandbox Credentials

Your sandbox key is on the dashboard the moment you sign up. Grab it from the Keys page (or the “Make your first API call” card on the dashboard). It's a self-contained Bearer token, so it's the only credential you need.

Note

Sandbox keys have the bak_test_ prefix (production keys use bak_live_). The SDK reads the prefix and targets the matching environment (https://sandbox.api.billerapi.com for sandbox) automatically — you never set a base URL.
Client portal dashboard after signup, with the sandbox API key ready to copy
3

Install the SDK & List Billers

Install the SDK, then list available billers — the simplest call to verify your key works.

Install
npm install billerapi
List billers
Node SDK
import { BillerApi, BillerApiError } from 'billerapi';

export async function listBillers(
  apiKey: string,
  log: (line: string) => void = console.log,
): Promise<void> {
  const billerapi = new BillerApi(apiKey);
  try {
    for await (const biller of await billerapi.billers.list()) {
      log(`${biller.id} ${biller.name} ready_for_bills=${biller.ready_for_bills}`);
    }
  } catch (error) {
    if (error instanceof BillerApiError) {
      log(`${error.code} request_id=${error.requestId ?? "unknown"}`);
    }
    throw error;
  }
}

HTTP reference (no SDK)

cURL
curl https://sandbox.api.billerapi.com/v1/billers \
  -H "Authorization: Bearer $BILLERAPI_API_KEY"

Tip

In sandbox, you'll see 6 test billers: Sandbox Utility, Sandbox Power, Sandbox Gas, Sandbox Water, Sandbox Electric, and Sandbox Telecom.
4

Link a Test Account

Linking is a two-part flow: your server creates a link token, then the browser SDK handles the user experience and returns a public token you exchange server-side.

Server: Create a link token

Node SDK
import { BillerApi } from 'billerapi';

export async function createLinkToken(apiKey: string) {
  const billerapi = new BillerApi(apiKey);
  return billerapi.links.createToken({
    client_user_id: 'user_123',
    biller_id: 'sb_utility',
    consents: ['bills:read'],
  });
}

// Send link_token to Connect. Keep link_token_id on your server.

HTTP reference (no SDK)

cURL
curl -X POST https://sandbox.api.billerapi.com/v1/link-tokens \
  -H "Authorization: Bearer $BILLERAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "client_user_id": "user_123",
    "biller_id": "sb_utility",
    "consents": ["bills:read"]
  }'

Server: Inspect continuation state

Keep link_token_id on your server. This status is authoritative: use next_action, blocking_reason, andretryable instead of inferring progress from elapsed time.

Node SDK
import { BillerApi, type LinkTokenStatusResult } from 'billerapi';

export async function inspectLinkToken(
  apiKey: string,
  linkTokenId: string,
): Promise<LinkTokenStatusResult> {
  const billerapi = new BillerApi(apiKey);
  const continuation = await billerapi.links.retrieveTokenStatus(linkTokenId);

  if (continuation.next_action === 'RETRY' && !continuation.retryable) {
    throw new Error('Connect retry was not approved');
  }
  return continuation;
}

Client: Open the Connect flow

Browser SDK
import { BillerApiElements } from 'billerapi-js';

export function openConnect(clientId: string, linkToken: string): void {
  const elements = new BillerApiElements({ clientId, environment: 'sandbox' });
  elements.connect({
    linkToken,
    onSuccess: async (publicToken, metadata) => {
      if (metadata.completion_mode === 'update') return;
      await fetch('/api/exchange-token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ public_token: publicToken }),
      });
    },
    onExit: (error) => {
      if (error) {
        console.error(error.code, error.blocking_reason, error.next_action, error.retryable);
      }
    },
  }).open();
}

Sandbox test credentials

Use account number 4242424242 with any username and password. This always succeeds. See the sandbox reference table below for other test scenarios.

Server: Exchange the public token

Node SDK
import { BillerApi } from 'billerapi';

export async function exchangeToken(apiKey: string, publicToken: string) {
  const billerapi = new BillerApi(apiKey);
  return billerapi.links.exchangeToken({ public_token: publicToken });
}

HTTP reference (no SDK)

cURL
curl -X POST https://sandbox.api.billerapi.com/v1/link-tokens/public-sandbox-.../exchange \
  -H "Authorization: Bearer $BILLERAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
5

Retrieve Bills

Use the link_id from Step 4 to fetch bills for the linked account. Bills are scoped to your API key — you pass the link, not the access token.

Fetch bills
Node SDK
import { BillerApi, type Bill } from 'billerapi';

export async function listBills(apiKey: string, linkId: string): Promise<Bill[]> {
  const billerapi = new BillerApi(apiKey);
  const bills: Bill[] = [];
  for await (const bill of await billerapi.bills.list({ account_link_id: linkId })) {
    bills.push(bill);
  }
  return bills;
}

HTTP reference (no SDK)

cURL
curl "https://sandbox.api.billerapi.com/v1/bills?account_link_id=$LINK_ID" \
  -H "Authorization: Bearer $BILLERAPI_API_KEY"
6

Listen for Webhooks

Register a webhook for the durable confirmation chain. Connect completion does not mean bills are visible yet: wait for connection.ready, then consumebill.created and bill.updated.

Register a webhook endpoint

Register endpoints with the SDK (the signing secret is returned once — persist it immediately), or from the Client Portal under Developer → Webhooks.

Node SDK
import { BillerApi } from 'billerapi';

export async function createWebhook(apiKey: string) {
  const billerapi = new BillerApi(apiKey);
  const endpoint = await billerapi.webhookEndpoints.create({
    url: 'https://your-server.com/webhooks/billerapi',
    events: ['connection.ready', 'bill.created', 'bill.updated'],
  });
  if (!endpoint.secret) throw new Error('Webhook signing secret was not returned');
  return { id: endpoint.id, secret: endpoint.secret };
}

Verify incoming events

Verify and parse each event in one call with billerapi.webhooks.constructEvent(), which throws if the HMAC signature or timestamp doesn't check out.

Node SDK
import { BillerApi } from 'billerapi';

interface BillCreatedEvent {
  id: string;
  object: 'event';
  type: 'bill.created';
  api_version: string;
  created: number;
  data: { object: { id: string; object: 'bill'; user_id: string; biller_id: string } };
  request: { id: string | null; idempotency_key: string | null };
  producer_service: string;
  correlation_id: string | null;
}

export function verifyWebhook(
  apiKey: string,
  rawBody: string | Buffer,
  signatureHeader: string | undefined,
  webhookSecret: string,
): BillCreatedEvent {
  const billerapi = new BillerApi(apiKey);
  return billerapi.webhooks.constructEvent<BillCreatedEvent>(
    rawBody,
    signatureHeader,
    webhookSecret,
  );
}

Note

For the exact signature header, retry policy, and all event types, see the Webhooks guide.

Develop locally with the CLI

No public URL yet? Use the BillerAPI CLI to stream live sandbox events straight to your local server — each event arrives as an HMAC-signed POST, exactly like production.

Terminal
# Authenticate the CLI with a sandbox key
billerapi login --env sandbox

# Forward live events to your local server (HMAC-signed)
billerapi listen --forward-to http://localhost:4242/webhooks

# In another terminal, fire a test event into the sandbox
billerapi trigger bill.created

Sandbox Reference

Use these magic account numbers in the Connect flow to trigger specific scenarios.

Account NumberScenarioBehavior
4242424242SuccessAlways succeeds. Returns a $127.50 pending bill.
4000000001Auth FailureReturns INVALID_CREDENTIALS error.
4000000002UnavailableReturns BILLER_UNAVAILABLE error.
4000000003MFA RequiredPrompts for MFA. Use code 123456.
4000000004LockedReturns ACCOUNT_LOCKED error.
4000000005Past DueReturns overdue bills ($245.00) with late fees.
4000000006Auto-PayReturns a scheduled $89.99 bill with auto-pay enabled.
4000000007Payment PlanReturns 3 installments of $150.00 each.
4000000008Multi-AccountAccount discovery returns multiple accounts.

Test Billers

Biller IDNameType
sb_utilitySandbox UtilityUtility
sb_powerSandbox PowerUtility
sb_gasSandbox GasUtility
sb_waterSandbox WaterUtility
sb_electricSandbox ElectricUtility
sb_telecomSandbox TelecomTelecom

Next Steps

Related API Reference

Was this page helpful?