Connection flow fundamentals
How BillerAPI links your users to their biller accounts.
A BillerAPI integration has two components. The first is end-user biller account linking, which your users complete through the hosted Elements flow. The second is data retrieval — reading bills and insights on the accounts your users have linked.
The lifecycle below walks a single connection from the moment your user taps Connect a biller to the moment bills and webhook events start arriving. Each stage names the actor responsible for it: your client, your server, or BillerAPI.
Your web or mobile app. Initiates linking and opens the hosted Elements flow. Never handles biller credentials.
Holds your API key. Creates link tokens, exchanges the public token, and stores the resulting access token.
Hosts the connect flow, authenticates the user with their biller, retrieves bills, and delivers webhook events.
Connection lifecycle
Five stages, in order. Stages 1 and 4 run on your server with your API key; stages 2 and 3 run in the browser; stage 5 is ongoing.
Create a link token
Your user initiates biller linking on your client. Your client calls your backend, and your backend creates a link token with POST /v1/link-tokens, passing client_user_id, an optional biller_id, and the consents your use case requires. Return the resulting link_token to your client.
POST /v1/link-tokens
const response = await fetch('https://sandbox.api.billerapi.com/v1/link-tokens', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $BILLERAPI_API_KEY',
},
body: JSON.stringify({
client_user_id: 'user_123',
biller_id: 'sb_utility',
consents: ['bills:read'],
}),
});
const { link_token } = await response.json();Link tokens expire after 30 minutes
Open Elements
Initiate the hosted flow with elements.connect({ linkToken, onSuccess, onExit }) from billerapi-js. Elements renders in an overlay on your page — your app keeps the surrounding experience, BillerAPI owns everything inside the frame.
Initialize and open Elements
import { BillerApiElements } from 'billerapi-js';
const elements = new BillerApiElements({
clientId: 'your_client_id',
environment: 'sandbox',
});
const handler = elements.connect({
linkToken,
onSuccess: async (publicToken, metadata) => {
// send publicToken to your server for exchange
},
onExit: (error) => { if (error) console.error(error.code); },
});
handler.open();Your user completes the hosted flow
Inside the frame, the user selects their biller, reviews and grants the consent scopes, picks a service region if that biller is region-gated, authenticates with the biller, clears MFA if required, and confirms which discovered accounts to link. When the flow finishes, onSuccess fires with a public_token.
Find your biller
Search supported billers
Share your bill data
Springfield Utility → Acme App
Log in to Springfield Utility
Entered here only — never sent to Acme App
Choose accounts to link
2 accounts found at Springfield Utility
The most common path. Region selection appears between consent and credentials for region-gated billers, and an MFA or security-question step appears after login when the biller requires one.
The full sequence, including every conditional step, is listed in Inside the hosted flow.
Sandbox test credentials
4242424242 with any username and password always succeeds — that is the happy path, with no challenge screens. To exercise MFA, use 4000000003 and enter code 123456 when the flow prompts. The full set of magic account numbers, including the error and multi-account scenarios, is in the Getting Started sandbox reference.Credentials never reach your servers
Exchange the public token
Send the public_token to your backend and exchange it with POST /v1/link-tokens/:public_token/exchange. You get back an access_token, the biller_id, and an account_link_id — store the last two against your user.
POST /v1/link-tokens/:public_token/exchange
const exchange = await fetch(
`https://sandbox.api.billerapi.com/v1/link-tokens/${encodeURIComponent(publicToken)}/exchange`,
{ method: 'POST', headers: { Authorization: 'Bearer $BILLERAPI_API_KEY' } },
);
const { access_token, biller_id, account_link_id } = await exchange.json();The link session that produced the token is readable for auditing and support:
The link_session object
{
"id": "ls_9f2c41d8a7b3",
"object": "link_session",
"client_user_id": "user_123",
"biller_id": "sb_utility",
"consents": ["bills:read"],
"status": "succeeded",
"account_link_id": "al_5e8d02c9f1a4",
"expires_at": "2026-08-23T18:30:00Z"
}Read bills and receive webhooks
With an account_link_id you can fetch bills for the linked account at any time.
GET /v1/bills?account_link_id=al_5e8d02c9f1a4
Authorization: Bearer $BILLERAPI_API_KEYLinking is only half synchronous. Everything that happens after the flow closes — the connection settling, new statements arriving, a link going stale — is delivered asynchronously by webhooks. Subscribe to the events your integration needs:
Don't poll for the first bill
GET /v1/bills can return an empty list. Wait for bill.created. See Webhook Delivery for signature verification and retry semantics.Inside the hosted flow
These are the steps your user moves through inside the Elements modal, in order. Steps adapt to the biller and to the session — only region-gated billers show the region step, a biller without MFA skips verification, a single-account biller skips account selection, and update or repair sessions skip consent.
| Step | Description |
|---|---|
| 1Select biller | User searches or picks their biller from supported billers. |
| 2Provide consent | User agrees to share the requested data (consent scopes shown). Skipped in update/repair mode. |
| 3Select region | Shown only for region-gated billers (for example Eversource CT/MA/NH). It sits between consent and credentials, because the region decides which login the biller presents. |
| 4Log in to biller | User authenticates with their biller credentials. |
| 5Verify (MFA) | Only if the biller requires it: one-time code or security question. |
| 6Select accounts | User picks which discovered accounts to link — this is the confirm-link step. |
| 7Success | Confirmation screen; the flow returns control to your app. |
Data permissions
Consent scopes decide what BillerAPI may read on a linked account. They are requested on the link token and shown to the user on the consent step.
| Data | Scope | Description |
|---|---|---|
| Bills | bills:read | Bill amounts, due dates, statement PDFs on linked accounts. |
| Account details | granted on link | Biller name, masked account identifier, link status. |
| Insights | derived from linked data | Usage and payment insights computed from linked accounts, where enabled. No separate scope is requested. |
Request only the scopes your use case needs. Users see the requested scopes on the consent step and must re-link to expand them.
How BillerAPI authenticates
Credentials are entered in the hosted flow only. Biller usernames and passwords are collected inside the Elements frame and never touch your servers — you never receive, proxy, or store them.
Region selection precedes the login, not the biller choice. A small number of billers serve several regions under one brand (Eversource CT/MA/NH, for example). For those, the hosted flow inserts a region step after consent and before credentials, because the region determines which login form the biller presents. Every other biller goes straight from consent to credentials.
MFA and security questions are handled inline. When a biller challenges the login with a one-time code or a security question, the hosted flow prompts the user in place and continues the session once it clears. No callback into your application is required.
Sandbox has a deterministic credential. Account number 4242424242 with any username and password always succeeds, so you can build and test the entire lifecycle before you have a real biller account.