Credential-less discovery
Let a user find their bills from their identity — no per-biller usernames or passwords. They enter a few identity fields, a provider returns the accounts it discovered across billers, and they one-tap to link them into the same Connect pipeline you already use.
Sandbox-only, default-OFF, preview surface
CREDENTIALLESS_DISCOVERY_ENABLED flag on sandbox-service and returns 404 while off. A real Method / Spinwheel / bureau adapter can slot in later behind the CredentiallessDiscoveryProvider seam — but only after the FCRA/GLBA permissible-purpose + legal sign-off called out in Before production.What it is & when to use it
The default Connect flow asks the user for the credentials of each biller they want to link. Credential-less discovery is a different entry point: the user supplies their identity instead, an aggregator returns the accounts it can attribute to that identity, and the user picks which ones to link. "Credential-less" means no per-biller username/password — it does not mean no link token.
The link token still rides over the SDK INIT message exactly like the embedded Connect flow. Once the user selects accounts, the existing account-selection → link pipeline completes and fires the link.completed webhook — identical to a credentialed link. Nothing downstream changes.
It reuses the Connect pipeline you already have
link.completed webhook is the authoritative signal that a link succeeded. See the Elements SDK and Webhooks guides for the pieces that carry over unchanged.Quick start
Construct a BillerApiElements client, then call discover() with a sandbox link token and a host element. Discovery renders inline into the element you provide — there is no modal overlay.
import { BillerApiElements } from 'billerapi-js';
// 1. One client for every flow
const elements = new BillerApiElements({
clientId: 'your_client_id',
environment: 'sandbox', // credential-less discovery is sandbox-only
});
// 2. Launch inline discovery with a sandbox link_token from your server
const handler = elements.discover({
linkToken: 'lt_xxx', // from your server
hostElement: document.getElementById('billerapi-discover'), // inline mount target
theme: 'light', // 'light' | 'dark' | ElementsTheme
onSuccess: (publicToken, metadata) => {
// Send publicToken to your server to exchange for an access token.
// The link.completed webhook is the source of truth.
console.log('Linked!', metadata);
},
onExit: (error) => {
if (error) console.error('Discovery error:', error.code, error.message);
},
onEvent: (eventName, metadata) => {
// Optional lifecycle telemetry
},
});
// 3. Render the secure iframe inline (no modal). Returns a Promise.
await handler.mount();
// handler.isMounted() -> boolean; handler.unmount() tears it down.Packages
billerapi-js (exposes elements.discover()); the React bindings are billerapi-react (exposes the useDiscover hook). Both are the same packages that power the rest of the Elements SDK.discover() & useDiscover()
Both take the same options object. The vanilla factory returns a handler; the hook returns the same handler methods plus reactive ready / error state.
| Option | Type | Required | Description |
|---|---|---|---|
| linkToken | string | Yes | Sandbox link token minted on your server; carried over the iframe INIT message. |
| hostElement | HTMLElement | Yes | The element to render the inline iframe into (no modal overlay). |
| onSuccess | (publicToken, metadata) => void | Yes | Fires once the user links their discovered accounts. Exchange the public token server-side. |
| theme | 'light' | 'dark' | ElementsTheme | No | Light/dark keyword or a full theme object (same shape as the other flows). |
| onExit | (error?) => void | No | Fires on user-cancel (no argument) or on error (an object with code + message). |
| onEvent | (eventName, metadata) => void | No | Optional lifecycle telemetry stream. |
Handler (returned by discover(), and surfaced by useDiscover()):
mount(): Promise<void>— render the inline iframe intohostElement.unmount(): void— tear it down (idempotent).isMounted(): boolean— current mount state.
The hook additionally returns ready (SDK loaded & safe to mount()) and error.
The identity model
The user submits four identity fields — all required, all snake_case on the wire. This mock deliberately collects no SSN and no date of birth: it is a PII-light surface by design.
| Field | Type | Notes |
|---|---|---|
| full_name | string | Required. |
| phone | string | Required. |
| string | Required; validated as an email address. | |
| postal_code | string | Required. |
No SSN, no DOB
The sandbox endpoint
The SDK drives this for you, but it maps to a single sandbox endpoint. The link token in the path is the credential — there is no API key header on this call, mirroring the other link-token routes on the sandbox host.
# Sandbox host serves sb_ / bak_test_ tokens.
# The {link_token} in the path is the credential (no secret header).
curl -X POST https://sandbox.api.billerapi.com/link/token/lt_xxx/discover-by-identity \
-H "Content-Type: application/json" \
-d '{
"full_name": "Ada Lovelace",
"phone": "5551234242",
"email": "ada@example.com",
"postal_code": "94105"
}'
# => 202 Accepted
# { "success": true, "discovered_count": 2 }
#
# When the CREDENTIALLESS_DISCOVERY_ENABLED flag is OFF (the default):
# => 404 Not FoundOn success the sandbox pre-loads the link session with the discovered accounts and emits the standard account-selection step. The user selects accounts, the existing exchange completes, and the link.completed webhook fires — the same terminal signal as any credentialed link.
Confirm outcomes server-side
onSuccess callback is a UX hint that runs in the user's browser. Treat the link.completed webhook as the authoritative record before fulfilling anything. See the Webhooks guide.The vendor-swappable provider seam
Discovery is implemented behind a provider interface so the aggregator is a swappable dependency. Today the only binding is a deterministic mock aggregator that returns synthetic liabilities. A real Method / Spinwheel / bureau adapter can be dropped in later by implementing the same interface — no change to the SDK, the endpoint contract, or the downstream link pipeline.
// The identity the caller submits (PII-light: no SSN / no DOB).
interface DiscoveryIdentity {
full_name: string;
phone: string;
email: string;
postal_code: string;
}
// A single discovered account/liability the user can one-tap to link.
interface DiscoveredLiability {
id: string; // 'sb_disc_acct_<hex>'
biller_id: string; // synthetic in the mock, e.g. 'sb_utility'
biller_name: string; // 'Sandbox Utility'
account_name: string;
mask: string; // '****4242'
type: string; // 'utility' | 'telecom' | 'insurance' | ...
}
// The swap point. The mock implements this today; a real vendor adapter
// (Method / Spinwheel / a bureau) implements the same method later.
interface CredentiallessDiscoveryProvider {
discover(identity: DiscoveryIdentity): Promise<DiscoveredLiability[]>;
}Mock behavior
Before production
Everything above is sandbox/mock. Pointing the CredentiallessDiscoveryProvider seam at a real aggregator or credit bureau moves you into regulated territory. A real adapter must not ship until all of the following are in place, with legal sign-off:
This gates any real-bureau adapter
CREDENTIALLESS_DISCOVERY_ENABLED in a non-sandbox environment — until the checklist below is satisfied and signed off.- FCRA permissible purpose. Pulling consumer liabilities from a credit bureau (or a bureau-derived aggregator) is a consumer-report activity under the Fair Credit Reporting Act. You need a documented permissible purpose, the user's informed consent/authorization captured before the pull, and the required disclosures.
- GLBA safeguards. The Gramm-Leach-Bliley Act privacy + safeguards rules apply to the nonpublic personal information involved. You need privacy notices, access controls, encryption in transit and at rest, retention/deletion policies, and a documented data-handling program.
- Expanded PII handling. A real bureau match typically needs FCRA-gated identifiers (e.g. SSN / DOB) that the mock intentionally omits. Any field beyond
full_name,phone,email,postal_codemust go through its own consent layer, minimization review, and secure storage design. - Vendor due diligence & contracts. Selecting a provider (Method, Spinwheel, or a bureau) requires vendor security/compliance review, a data processing agreement, and confirmation the vendor's own permissible-purpose and consumer-consent posture matches your use case.
- Legal sign-off. All of the above must be reviewed and approved by legal/compliance before the seam is bound to a live provider in any non-sandbox environment.