# Link Demo Playground

Launch the real hosted Connect flow against BillerAPI's sandbox — no real biller and no integration code. Sign in, paste your sandbox client id, pick a biller and a scenario, and step through Connect exactly as your users would.

> **Note — Everything here runs against sandbox**
> The playground mints a **sandbox** `link_token` using the sandbox API key from your signed-in dashboard and opens the hosted `/connect` page in a new tab. No real biller is contacted and no money moves. Grab a sandbox client id at [/developer/keys](/developer/keys).

## The interactive playground

The page embeds an interactive panel. Its controls and copy:

- **Sandbox client id** — a text input, placeholder `Client ID from /developer/keys`. Get a sandbox client id at [/developer/keys](/developer/keys). When you're signed in, we pair it with your sandbox secret automatically to mint the token.
- **Biller** — a picker over the deterministic sandbox billers. Each biller is bound to one outcome:

| Biller | Biller id | Outcome | Magic account number | Description |
| --- | --- | --- | --- | --- |
| Sandbox Utility | `sb_utility` | Successful link | `4242424242` | Credentials accepted; one residential account; one bill due in 15 days. |
| Sandbox Power | `sb_power` | MFA required | `4000000003` | MFA challenge. Continue with code 123456. |
| Sandbox Gas | `sb_gas` | Security question | `4000000011` | Knowledge-based challenge. Answer "pass_answer". |
| Sandbox Water | `sb_water` | Slow verification | `4000000010` | Credentials accepted; verification completes in the background (~90s). |
| Sandbox Electric | `sb_electric` | Auth failure | `4000000001` | submit-credentials returns success: false. Exercise your invalid-credentials UX. |
| Sandbox Telecom | `sb_telecom` | Will retry | `4000000012` | Login fails transiently; the platform re-drives it automatically. |

- **Outcome** — the selected biller's scenario label and description.
- The hosted flow uses this biller's canonical magic account automatically — any username/password drives the selected scenario. See the [magic account numbers](/docs/sandbox/magic-numbers) reference for every scenario.
- Error copy the panel can report: "Paste your sandbox client id first." when the client id is empty, and "Sign in to your BillerAPI dashboard to mint a sandbox link token here, or run the server-side snippet." when you are not signed in.
- **Launch sandbox Connect** — Mints a sandbox link_token for the selected biller and opens `/connect` in a new tab, via a **Launch in new tab** button. On success the panel reports `scenario`, `biller_id`, and `link_token_id`. If the browser blocks the new tab it reports "Link minted — the new tab was blocked" and offers the Connect URL as a plain link; otherwise "Link minted — new tab opened".
- [Open Connect from your own app](/docs/guides/elements-sdk)

Mint the link_token **server-side** — your secret never reaches the browser. The Launch button above does it client-side with your signed-in sandbox secret. That's fine for a demo, but never ship your secret to the browser in production.

**cURL**

```bash
# Mint a sandbox link_token, then open
# /connect?linkToken=<link_token>&client_id=<your sandbox client id>
# (both query params are required — /connect cannot initialize from one alone)
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": "docs_demo", "biller_id": "sb_utility" }'

# => { "link_token": "sb_link_tok_...", "link_token_id": "sb_ltk_...", "expires_at": "..." }
```

**Node**

```javascript
// Mint a sandbox link_token server-side, then hand the token to the SDK.
const res = await fetch('https://sandbox.api.billerapi.com/v1/link-tokens', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer $BILLERAPI_API_KEY', // your sandbox secret, server-side only
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ client_user_id: 'docs_demo', biller_id: 'sb_utility' }),
});
const { link_token } = await res.json();

// In the browser, launch Connect with the minted token:
// new BillerApiElements({ clientId, environment: 'sandbox' })
//   .connect({ linkToken: link_token, onSuccess, onExit })
//   .open();
```

## How the flow fits together

1. **Server mints a link_token** — POST /v1/link-tokens with your client id + API key. Sandbox keys use the bak_test_ prefix; production keys use bak_live_. The mint belongs on YOUR backend.
2. **Browser launches Connect with the token** — The Elements SDK .connect({ linkToken }).open() opens the hosted /connect iframe. The playground opens it in a new tab instead so you can watch the whole flow.
3. **onEvent streams the funnel** — As the user moves through panes, the hosted page emits connect/* events to your onEvent handler — connect/view, connect/authenticated, connect/error, and so on.
4. **Webhooks confirm the outcome** — link.completed (interactive) or link_token.completed (backgrounded) is the authoritative signal that the link is ready. SDK callbacks are UX hints only.

## Next steps

- → [Elements SDK guide](/docs/guides/elements-sdk) — wire `.connect()` into your own app.
- → [Connect Events reference](/docs/api/connect-events) — the full `connect/*` onEvent taxonomy.
- → [Magic account numbers](/docs/sandbox/magic-numbers) — every sandbox scenario and the account number that triggers it.
- → [Set up webhooks](/docs/guides/webhooks) — confirm link outcomes server-side.
