Portals & Cross-Workspace Queries
Manage multiple client portals from one parent workspace. Query banks, feeds, integrations, and jobs across all portals with the scope=family parameter.
4 min read
On this page
If you're an accountant managing multiple clients, BankSync lets you organise each client into a Portal: a dedicated workspace they own, billed to you. This guide explains how to query data across your portals programmatically with the scope=family parameter.
What's a portal?#
A portal is a child workspace under your parent workspace. Each portal has its own:
- Bank connections (the client's own, isolated from yours)
- Members (the client themselves, plus anyone they invite if you allow it)
- Feeds, integrations, jobs
- Soft-delete + 30-day cleanup lifecycle
Your portals roll up to your plan: one Stripe subscription pays for the parent and every portal under it. The parent's owner and admins have implicit owner / admin authority on every portal, no separate invitation needed.
Who can use cross-workspace queries
Family-scoped reads require admin or owner role on the parent workspace. Portal-direct members (clients you've invited) cannot use them: they only see their own portal's data.
The scope query parameter#
Every list endpoint accepts an optional scope query parameter that controls what set of workspaces the query operates over.
| Value | Returns | Requires |
|---|---|---|
self (default) | Just this workspace's rows | The endpoint's standard role |
family | This workspace + every live portal under it. Each row gains workspaceId and portal | Admin+, top-level workspaces only |
Endpoints supporting scope=family today:
GET /v1/banksGET /v1/feedsGET /v1/integrationsGET /v1/jobs(internal API only for now)
Example: list every bank across all your portals#
Your parent-workspace API key works for this: no need to mint a separate key per portal.
curl -X GET "https://api.banksync.io/v1/banks?scope=family" \ -H "X-API-Key: bsk_your_api_key_here" \ -H "Content-Type: application/json"const response = await fetch('https://api.banksync.io/v1/banks?scope=family', { headers: { 'X-API-Key': process.env.BANKSYNC_API_KEY, 'Content-Type': 'application/json', },})const { data: banks } = await response.json()for (const bank of banks) { if (bank.portal) { console.log(`${bank.name} - ${bank.portal.name}`) } else { console.log(`${bank.name} - (my workspace)`) }}import osimport requests
response = requests.get( "https://api.banksync.io/v1/banks?scope=family", headers={ "X-API-Key": os.environ["BANKSYNC_API_KEY"], "Content-Type": "application/json", },)for bank in response.json()["data"]: where = bank["portal"]["name"] if bank.get("portal") else "(my workspace)" print(f"{bank['name']} - {where}")Response shape#
Each row gains two fields when scope=family is requested:
{ "success": true, "data": [ { "id": "bnk_abc", "name": "Chase Checking", "workspaceId": "ws_parent", "portal": null, ... }, { "id": "bnk_xyz", "name": "Acme Co Operating", "workspaceId": "ws_acme_portal", "portal": { "id": "ws_acme_portal", "name": "Acme Co", "icon": "🏢" }, ... } ]}workspaceId: the workspace the row actually lives in. For your own rows this is the parent workspace; for portal rows this is the portal's id.portal: attribution badge.nullfor rows owned by the parent; a{ id, name, icon? }object for rows owned by a portal.
Targeting a single portal#
Portals are workspaces: you can address one directly via its workspace id. Your parent-scoped API key is automatically authorised against every live portal you own; no per-portal key needed.
# List banks in one specific client's portalcurl -X GET "https://api.banksync.io/v1/banks" \ -H "X-API-Key: bsk_your_api_key_here" \ -H "X-Workspace-Id: ws_acme_portal"# Or use the workspace id in the URL path (depending on the endpoint).The credential still has to come from your parent workspace (or be a Firebase session). A key minted on workspace A cannot reach into workspace B's portals, only into A's own.
Error responses#
| Status | When |
|---|---|
400 | Unknown scope value (anything other than self / family), or scope=family was sent against a portal workspace |
403 | Your role on the parent workspace is editor or viewer: scope=family requires admin+ |
404 | The workspace id in the URL doesn't exist or was deleted |
Using portals with MCP#
Connect an MCP client (Claude Code, Cursor, etc.) to your parent workspace once: your portals appear automatically.
list_workspacessurfaces every portal you manage withisInherited: trueand aparentWorkspaceId.- All workspace-scoped tools (
list_banks,list_feeds, …) accept any portal'sworkspaceIdyou own. - See the MCP setup guide for connecting your client.
For clients, not advisors
If you've been invited into a portal by an accounting firm, programmatic access is intentionally not available: your accountant manages API keys and MCP connections on your behalf. The portal Developer Settings tab is hidden for portal-direct members.
Internal API vs public REST API#
Two surfaces ship the same scope=family contract:
- Public REST API (
api.banksync.io/v1/*): the surface most third-party integrations use. Banks, feeds, integrations all accept?scope=family. - Internal API (
internal-api.banksync.io/api/*): used by the BankSync web app and MCP server. Same scope semantics, plus a/jobsworkspace-aggregate endpoint.
See also#
- Authentication: API keys, scopes, and bearer tokens
- API reference: every endpoint and its response shape
- MCP setup: connect Claude Code or other MCP clients
Use this page with your AI assistant
Every BankSync doc is available as plain Markdown for agents and LLMs.