Account-First Model
Understand Finatic's account-first identity model, environments, session flow, grants, and account-scoped API paths.
Finatic is account-first: your company authenticates with API keys, the user connects a broker in Connect, and your app reads or trades only against financial account IDs they granted.
See Account model for the short overview.
Identity model
| Concept | In your app |
|---|---|
| Company workspace | API keys, sessions, webhooks — dashboard accountId / companyAccountId |
| Financial account | accountId on /api/v1/accounts/{accountId}/... routes and SDK calls |
| Provider connection | Internal to Finatic — do not store or expose to customers |
Persist company account ID, your user ID, and granted financial accountId values. Do not build on legacy beta connectionId or user_broker_connection_id.
End-to-end flow
- Backend uses its API key in
X-API-Keyto mint a 90-second one-time token (v1.getToken()/v1.get_token()). - Browser starts the Client SDK session and opens Connect with that token.
- User signs in to the broker and selects accounts.
- Connect creates grants (read, optional trading).
- Wait until data is ready — poll session sync status or use per-account webhooks (
account.grant.created,account.sync.succeeded). No session-level “all ready” webhook yet. - Read or trade via account-scoped routes, e.g.
/api/v1/accounts/{accountId}/positions.
Backend: one-time token
1import { FinaticServer } from '@finatic/server-node';
2
3const finatic = new FinaticServer(process.env.FINATIC_API_KEY!, {
4 apiEnvironment: 'sandbox',
5});
6
7const oneTimeToken = await finatic.v1.getToken();
8// 90 seconds. Send oneTimeToken to the browser only.Browser: Connect and first reads
1import { FinaticConnect } from '@finatic/client';
2
3const finatic = await FinaticConnect.init('one-time-token', undefined, {
4 apiEnvironment: 'sandbox',
5});
6
7type AccountSummary = { accountId: string; syncStatus?: string };
8
9await finatic.openPortal({
10 mode: 'dark',
11 onSuccess: (userId) => console.log('Portal user linked:', userId),
12 onEvent: async (eventName) => {
13 if (eventName !== 'account.grant.created') return;
14 const accounts = await finatic.v1.listAccounts<AccountSummary[]>({ includeSyncStatus: true });
15 const account = accounts.success?.data.find((item) => item.syncStatus === 'ready');
16 if (account) {
17 await finatic.v1.listPositions({ accountId: account.accountId });
18 }
19 },
20});Backend: check sync readiness
The released server facades do not yet wrap session sync status. Poll the released REST endpoint with your API key:
1const response = await fetch(
2 `https://api.finatic.dev/api/v1/sessions/${encodeURIComponent(sessionId)}/sync-status`,
3 { headers: { 'X-API-Key': process.env.FINATIC_API_KEY! } },
4);
5if (!response.ok) throw new Error(`Sync status failed: ${response.status}`);
6
7const result = await response.json() as {
8 success?: { data: { status: string; accounts: Array<{ accountId: string }> } };
9};
10if (result.success?.data.status === 'ready') {
11 const accountIds = result.success.data.accounts.map((account) => account.accountId);
12}Environments
| Environment | Behavior |
|---|---|
live | Real broker auth and data (including broker paper accounts) |
sandbox | Finatic synthetic test data only |
Do not mix sandbox and live account IDs.