MCP tools
The BankSync MCP server exposes 143 tools to AI agents (67 read-only). Connect it to your client from the setup guides, then the agent can call any of these.
Discovery
Discover available capabilities, data schemas, and feed source types.
search_docsread-onlySearch the BankSync product documentation for how-to, setup, and feature guides. Returns matching doc titles, sections, and descriptions with a slug — NOT the body. Call read_doc with a slug to get the full guide. Use this for product/feature/how-to questions before answering.
Parameters
querystringrequiredKeywords or a natural-language question about using BankSync.
read_docread-onlyRead the full Markdown of a BankSync documentation page by slug (from search_docs). Ground your answer in it and reference the doc URL so the user can read more.
Parameters
slugstringrequiredThe doc slug from a search_docs result, e.g. "australian-banks".
Workspace
Manage workspaces and connected destination integrations.
list_workspacesread-onlyList all workspaces the authenticated user can access. Returns workspaceId, name, role, and (for accountants) any portals managed via parent inheritance. `isInherited: true` marks portals the user reaches through their owner/admin role on `parentWorkspaceId` — useful for distinguishing the accountant's clients from their own workspaces.
No parameters.
get_workspaceread-onlyGet name, subscription plan, and settings for the current workspace. Returns 403 if the user is not a member.
No parameters.
list_integrationsread-onlyList connected destination integrations (Notion, Google Sheets, Airtable) for the workspace. Returns integrationId needed when configuring a feed destination. Pass scope='family' on an accountant's parent workspace to also include integrations from every portal under it — each row gains a `portal: { id, name, icon } | null` badge and a `workspaceId` field. Provider auth tokens are stripped from all responses.
Parameters
scope"self" | "family"'self' (default): integrations in this workspace only. 'family': also include integrations from every portal under this workspace. Requires admin+ role; only valid on a top-level workspace.
delete_integrationwriteretry-safeRemove a destination integration (Notion, Airtable, Google Sheets) from the workspace. Any feeds using this integration as their destination will fail to sync after removal. To add a new integration, use the BankSync app to complete the OAuth authorization flow.
Parameters
integrationIdstringrequiredIntegration ID to remove — get from list_integrations
Portals (Accountants)
For accountants managing multiple clients: list, create, update, and delete portals; check family-wide plan usage.
list_portalsread-onlyList the client portals under this parent workspace (id, name, icon, bank/feed/member/invitation counts, portalConfig, lastActivityAt). Owner/admin only.
No parameters.
get_family_usageread-onlyGet total resource counts (banks, feeds, integrations, members, portals) and effective family limits across this workspace and all its portals. Owner/admin only.
No parameters.
create_portalwritedo not retryCreate a new client portal under this parent workspace. Optionally invite the client by email. Owner/admin only.
Parameters
namestringrequiredClient business name for the portaldescriptionstringInput for description.iconstringOptional emoji iconclientEmailstringIf set, sends an invitation to the clientclientRole"editor" | "viewer"Invited client role — default editorcapabilitiesobjectPer-portal member capabilities. Default: client CAN connect banks, CANNOT create feeds or integrations.limitsobjectPer-portal caps. null = no cap beyond the family ceiling.
update_portalwriteretry-safePartially update a client portal (name, description, icon, member capabilities, per-portal limits). Owner/admin only.
Parameters
portalIdstringrequiredPortal ID — get from list_portalsnamestringInput for name.descriptionstring | nullnull clears the descriptioniconstring | nullnull clears the iconcapabilitiesobjectPer-portal member capabilities. Default: client CAN connect banks, CANNOT create feeds or integrations.limitsobjectPer-portal caps. null = no cap beyond the family ceiling.
delete_portalwriteretry-safeSoft-delete a client portal. Bank connections are cleaned up over the next 30 days. Refuses if the portal's banks are still referenced by parent-workspace feeds. Owner/admin only.
Parameters
portalIdstringrequiredPortal ID to delete — get from list_portals
Bank Connections
Connect, list, and manage bank/institution connections.
list_banksread-onlyList bank connections in the workspace. Returns bankId needed for list_accounts, get_transactions, and other data tools. Use scope:'family' to also include banks from every portal under this workspace (admin+); each row gains a `portal` badge and a `workspaceId` field.
Parameters
scope"self" | "family"'self' (default): banks in this workspace only. 'family': also include banks from every portal under this workspace. Requires admin+ role; only valid on a top-level workspace.
get_bankread-onlyGet a bank connection and its accounts from the aggregator. Returns bankId, institution name, and accounts with accountId and accountType. The account list may be served from a cache up to 6 hours old; `cachedAt` is present when it was.
Parameters
bankIdstringrequiredBank connection ID — get from list_banks
create_bank_linkwritedo not retryInitiate a new bank connection for any supported provider. Pass a providerInstanceId (e.g. "plaid:us") to start the flow. Returns a ConnectionStart payload (Plaid Link token, portal popup URL, or OAuth redirect URL). The expectedPayloadKind field tells you what to pass to connect_bank. Use search_institutions first.
Parameters
providerInstanceIdstringrequiredCanonical provider instance ID, e.g. "plaid:us", "saltedge:uk" (UK), "saltedge:sandbox" (fake banks, non-prod), "saltedge:eu" (EEA — Germany, France, Spain, and 27 more), "snaptrade:default". Use search_institutions to find the right one.emailstringUser email address — required for SnapTrade and SaltEdge workspace registration. Not needed for Plaid.institutionIdstringPre-select a specific institution by provider ID (a way's providerInstitutionId from search_brands / get_brand). Omit to let the user search, or when the way's targeting is 'picker'.brandIdstringThe bank this connection is for, as returned by search_brands / get_brand (`key`). Required for a way whose targeting is 'picker' (it has no providerInstitutionId): the provider's own picker opens on the bank's name, which is resolved server-side from this id. Harmless alongside institutionId.countryCodesstring[]ISO 3166-1 alpha-2 country codes to scope the connection, e.g. ["AU"] or ["US"]redirectUristringOAuth redirect URI for redirect-based flowsbrokerageSlugstringSnapTrade: brokerage slug to pre-select in the portal (skips the picker step)connectionType"read" | "trade"SnapTrade: initial connection scope — defaults to "read"
connect_bankwritedo not retryComplete a bank connection by exchanging the provider callback payload. Call create_bank_link first, then pass the callback payload here. For Plaid: payloadKind="plaid_public_token" + publicToken. For SaltEdge/SnapTrade: payloadKind="provider_connection_id" + connectionId.
Parameters
providerInstanceIdstringrequiredSame providerInstanceId used in create_bank_link, e.g. "plaid:us"payloadKind"plaid_public_token" | "provider_connection_id"requiredMatches the expectedPayloadKind returned by create_bank_link. Use "plaid_public_token" for Plaid, "provider_connection_id" for SaltEdge/SnapTrade.publicTokenstringPlaid public_token from Plaid Link onSuccess — required when payloadKind is "plaid_public_token"institutionIdstringInstitution ID from Plaid Link metadata (optional, for display)connectionIdstringProvider connection ID from the callback redirect — required when payloadKind is "provider_connection_id"
search_institutionsread-onlyDeprecated: prefer search_brands, which returns one result per bank with every way to connect it. Search for financial institutions across all active provider instances. Returns a merged list with providerInstanceId — pass that to create_bank_link to start a connection. Narrow by country code or target a single provider with providerInstanceId.
Parameters
querystringrequiredInstitution name or keyword to search forcountryCodesstring[]ISO 3166-1 alpha-2 filter, e.g. ["AU", "GB"]. Omit to search all regions.providerInstanceIdstringLimit search to one provider, e.g. "saltedge:uk" for UK banks or "saltedge:eu" for European (EEA) banks. Omit for multi-provider fan-out.
delete_bankwriteretry-safeRemove a bank connection from the workspace. Fails with a conflict if any sync feed still reads from the bank — repoint or delete those feeds first (list_feeds shows sources). By default the removal is reversible until the monthly cleanup; pass permanent to revoke at the provider and destroy the data now.
Parameters
bankIdstringrequiredBank ID to remove — get from list_bankspermanentbooleanRemove the bank for good: revoke the connection at the provider, destroy every row derived from it, and delete it immediately instead of at the next monthly cleanup. IRREVERSIBLE — the bank cannot be reactivated afterwards. Requires the admin or owner role. Defaults to false (reversible).
search_brandsread-onlySearch banks by name, alias, routing number or web address. Returns ONE result per bank (`key`) with every way to connect it (providerInstanceId + providerInstitutionId), which way we recommend, and how reliable each has been for that bank. Each way carries `targeting`: 'portal' means pass its providerInstanceId and providerInstitutionId to create_bank_link; 'picker' means the way has NO providerInstitutionId — the provider's own picker opens on the bank's name — so pass its providerInstanceId and the bank's `key` as brandId instead. Banks are market-scoped: pass the ISO country code.
Parameters
querystringrequiredBank name, alias, routing number or web address to search formarketstringrequiredISO 3166-1 alpha-2 market, e.g. "US", "AU". Banks are market-scoped.limitintegerMaximum banks to return.
get_brandread-onlyGet one bank and every way to connect it: the recommended provider, how reliable each has been for this bank, how much history that judgement rests on, whether the login is an OAuth hand-off, and any current warning. Each way carries `targeting`: 'portal' means pass its providerInstanceId and providerInstitutionId to create_bank_link; 'picker' means the way has NO providerInstitutionId (the provider's own picker opens on the bank's name), so pass its providerInstanceId and this bank's `key` as brandId instead.
Parameters
brandIdstringrequiredBank id, as returned by search_brands (`key`).
list_accountsread-onlyFetch the accounts on a bank connection from the aggregator. Returns accountId, accountType (checking/savings/investment/loan), balance, currency, and `balanceAsOf` (when the aggregator last refreshed that balance at the institution — absent means unknown, never 'just now'). The list may be served from a cache up to 6 hours old, and the balances are the aggregator's latest snapshot and can lag the bank; use get_balance (which reports `cachedAt`) when the age matters. Use accountId with get_transactions, get_balance, etc.
Parameters
bankIdstringrequiredBank connection ID — get from list_banks
get_accountread-onlyFetch a single account's details from the aggregator — the same record `list_accounts` returns, narrowed to one accountId. Returns accountType, balance, currency, institution name, and `balanceAsOf` (when the aggregator last refreshed that balance at the institution; absent means unknown, never 'just now'). The balance is the aggregator's latest snapshot and can lag the bank; this is NOT a live institution read. The record may be served from a cache up to 6 hours old; `cachedAt` is present when it was. Use list_accounts to discover valid accountIds.
Parameters
bankIdstringrequiredBank connection ID — get from list_banksaccountIdstringrequiredAccount ID — get from list_accounts or get_bank
get_transactionsread-onlyFetch bank transactions for an account. By default returns the most recent 30 days; pass from/to for a specific date range and retain those dates across pages. Pass nextCursor back as cursor while hasMore=true. Set sync=true (Plaid only) for added/modified/removed changes since syncCursor. Retain the original update-start cursor and all uncommitted changes until hasMore=false; on pagination restart advice discard the uncommitted attempt and restart once from that original cursor. Never reset a saved feed cursor. Date-range results may be served from a cache up to 6 hours old (`cachedAt` is present when they were); sync=true results are always live.
Parameters
bankIdstringrequiredBank connection ID — get from list_banksaccountIdstringrequiredAccount ID — get from list_accounts or get_bankfromstringStart date in YYYY-MM-DD format, e.g. "2024-01-01" (default: 30 days ago).tostringEnd date in YYYY-MM-DD format, e.g. "2024-12-31" (default: today).cursorstringPagination token for date-range results: the `nextCursor` from a previous non-sync response. Retain the original from/to dates. Cannot be combined with sync=true.syncbooleanPlaid only. When true, return transactions added/modified/removed since `syncCursor` instead of a date range. Use for keeping a local copy in sync; omit it for ordinary "show recent transactions" reads.syncCursorstringIncremental-sync token, only with sync=true. Retain the original update-start cursor until hasMore=false. On restart_pagination discard uncommitted pages and restart from that original cursor, at most once. Never reset a saved feed cursor. Omit only for an intentional initial sync.
get_balanceread-onlyFetch the balance for an account from the bank aggregator. Returns current balance, available balance, and credit limit where applicable. IMPORTANT: this reads the aggregator's most recent snapshot, which it refreshes on its own schedule (often only once a day, and for brokerage accounts only after the institution posts an update) — it does NOT force the institution to recompute. Read `balanceAsOf` for when that data was actually refreshed and `retrievedAt` for when BankSync read it from the aggregator; if they differ by more than you expect, the balance is genuinely behind the bank and no BankSync action will change that. `balanceAsOf` is null when the provider does not report a refresh time — that means UNKNOWN, never fresh. The answer may be served from a cache up to 6 hours old: `cachedAt` is then present and equals `retrievedAt`.
Parameters
bankIdstringrequiredBank connection ID — get from list_banksaccountIdstringrequiredAccount ID — get from list_accounts or get_bank
get_holdingsread-onlyGet current investment holdings (positions, quantities, market values) for an investment account. Only valid for accounts with accountType="investment" — use list_accounts to check. Holdings may be served from a cache up to 6 hours old.
Parameters
bankIdstringrequiredBank connection ID — get from list_banksaccountIdstringrequiredInvestment account ID — must have accountType="investment", get from list_accounts
get_tradesread-onlyGet investment transactions (buys, sells, dividends) for an investment account over a date range. Only valid for accounts with accountType="investment" — use list_accounts to check. Results may be served from a cache up to 6 hours old; `cachedAt` is present when they were.
Parameters
bankIdstringrequiredBank connection ID — get from list_banksaccountIdstringrequiredInvestment account ID — must have accountType="investment", get from list_accountsfromstringStart date in YYYY-MM-DD format, e.g. "2024-01-01" (default: 90 days ago)tostringEnd date in YYYY-MM-DD format, e.g. "2024-12-31" (default: today)
get_loanread-onlyGet loan details (outstanding balance, interest rate, next payment) for a loan or liability account (mortgage, personal loan, credit card). Use list_accounts to find accounts with accountType="loan" or "credit". Results may be served from a cache up to 6 hours old; `cachedAt` is present when they were.
Parameters
bankIdstringrequiredBank connection ID — get from list_banksaccountIdstringrequiredLoan/liability account ID — get from list_accounts, accountType should be "loan" or "credit"
Feed Management
Create and manage data pipeline feeds with field mappings.
list_feedsread-onlyList the data feeds in the workspace (id, name, source, dataType). Use scope:"family" to include portal feeds (admin+).
Parameters
scope"self" | "family"'self' (default): this workspace only. 'family': include portals. Admin+ only.
get_feedread-onlyGet the full configuration of one feed by id.
Parameters
feedIdstringrequiredFeed ID — get from list_feeds
validate_feedread-onlyDry-run validate a feed configuration (new feed if no feedId, update if feedId). Returns errors + warnings WITHOUT writing.
Parameters
feedIdstringIf provided, validate as an update to this feednamestringInput for name.source"sync" | "extractor" | "upload"Required when validating a NEW feeddataType"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"Required when validating a NEW feedsourceConfigunknownSource configuration. type must match the feed source: "sync" → bank accounts; "extractor" → email inbox; "upload" → file uploaddestinationConfigunknownDestination configuration. type determines the provider: "notion", "airtable", "google_sheets", "excel", "database", "webhook", "table", or "app". Get integrationId from list_integrations.fieldMappingsobject[]Maps source fields to destination columns/properties.dataTypeOptionsunknownData-type-specific processing options (only for sync feeds)extractionConfigobjectAI extraction configuration for extractor/upload feedsscheduleobjectAutomated sync schedule
create_feedwritedo not retryCreate a new data-pipeline feed. Validate first with validate_feed. destinationConfig + fieldMappings can be added before the first sync.
Parameters
namestringrequiredHuman-readable feed name, e.g. "ANZ Transactions → Notion"source"sync" | "extractor" | "upload"required"sync"=bank API; "extractor"=email/PDF AI; "upload"=filedataType"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"requiredType of financial data this feed producessourceConfigunknownSource configuration. type must match the feed source: "sync" → bank accounts; "extractor" → email inbox; "upload" → file uploaddestinationConfigunknownDestination configuration. type determines the provider: "notion", "airtable", "google_sheets", "excel", "database", "webhook", "table", or "app". Get integrationId from list_integrations.fieldMappingsobject[]Maps source fields to destination columns/properties.dataTypeOptionsunknownData-type-specific processing options (only for sync feeds)extractionConfigobjectAI extraction configuration for extractor/upload feedsscheduleobjectAutomated sync scheduleiconstringOptional emoji icon
update_feedwriteretry-safePartially update a feed (name, config, schedule). source + dataType are immutable.
Parameters
feedIdstringrequiredFeed ID — get from list_feedsnamestringNew feed nameiconstringNew iconsourceConfigunknownSource configuration. type must match the feed source: "sync" → bank accounts; "extractor" → email inbox; "upload" → file uploaddestinationConfigunknownDestination configuration. type determines the provider: "notion", "airtable", "google_sheets", "excel", "database", "webhook", "table", or "app". Get integrationId from list_integrations.fieldMappingsobject[]Maps source fields to destination columns/properties.dataTypeOptionsunknownData-type-specific processing options (only for sync feeds)extractionConfigobjectAI extraction configuration for extractor/upload feedsscheduleobjectAutomated sync schedule
delete_feedwriteretry-safePermanently delete a feed. This cannot be undone.
Parameters
feedIdstringrequiredFeed ID to delete — get from list_feeds
get_data_type_schemaread-onlyGet the available source fields for a given data type — use this before building fieldMappings in create_feed or update_feed. Returns each field's key (use as sourceField), name, type, description, and whether it is required. Required fields must be mapped before a sync will complete successfully. For extractor/upload feeds, fields are user-defined in extractionConfig — this tool only covers bank sync (sync source) data types.
Parameters
dataType"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"requiredThe data type to get the field schema for. Bank sync types: transactions, balances, trades, holdings, loans. Extractor/upload types (receipts, invoices, documents) use user-defined fields in extractionConfig.
list_feed_sourcesread-onlyList all supported feed source types and the data types each source can produce. Use this to discover valid source × dataType combinations before calling create_feed. Also describes what each source type does and what configuration it needs.
No parameters.
Job Management
Trigger syncs, track job progress, and manage the sync queue.
list_jobsread-onlyList sync/learn jobs. With feedId: jobs for that feed (filter by status, limit). Without feedId: recent jobs across all feeds (scope:"family" includes portal feeds, admin+).
Parameters
feedIdstringFeed ID — get from list_feeds. Single-feed mode.scope"self" | "family"'self' (default): this workspace only. 'family': include portals. Admin+ only.limitnumberSingle-feed mode: max jobs to return (default 20, cap 25).limitPerFeednumberCross-feed mode: max jobs per feed (default 5, max 25).status"created" | "in_progress" | "paused" | "completed" | "failed" | "cancelled"Single-feed mode only: filter by job status.
get_jobread-onlyGet the full details of one job by id (status, progress, errors, counts).
Parameters
feedIdstringrequiredFeed ID — get from list_feedsjobIdstringrequiredJob ID — get from list_jobs
trigger_syncwritedo not retryManually start a sync run for a feed. Pulls the latest data from the source and writes it to the destination.
Parameters
feedIdstringrequiredFeed ID to sync — get from list_feeds
trigger_learnwriteretry-safeTrigger a learn cycle for a feed (refreshes Memory enrichment patterns). Returns triggered:false with a reason when the feed has no Memory enrichment or a destination that cannot be read back.
Parameters
feedIdstringrequiredFeed ID to learn — get from list_feeds
cancel_jobwriteretry-safeCancel an in-progress job. Has no effect on jobs that have already finished.
Parameters
feedIdstringrequiredFeed ID — get from list_feedsjobIdstringrequiredJob ID to cancel — get from list_jobs
Enrichment
Inspect every enrichment in a workspace, dry-run the pipeline on sample records, and build rules, alerts and Memory for auto-categorization.
list_enrichmentsread-onlyList all enrichments in the workspace (id, name, type — any of the 16 enrichment types — enabled, dataType, order, feed associations).
No parameters.
get_enrichmentread-onlyGet the full configuration of one enrichment by id, including its type-specific config block (ruleConfig, alertConfig, filterConfig, budgetConfig, …).
Parameters
enrichmentIdstringrequiredEnrichment ID — get from list_enrichments
preview_enrichmentread-onlyRun the full enrichment pipeline (guards → memory → lookups → rules → transforms → AI fill → watchers) on sample records WITHOUT persisting anything. Returns each record original/enriched/changedFields plus per-enrichment match counts; guard-suppressed rows and split children are reported, and alerts are dry-run. Pass up to 50 records.
Parameters
feedIdstringrequiredFeed ID — get from list_feedsrecordsobject[]requiredSample records to enrich (max 50). Keys should match the feed dataType field names, e.g. { description, amount, date, category }
create_enrichmentwritedo not retryCreate a new enrichment of any of the 16 types. Transform (rule, lookup) sets field values; guard (filter, split, transfer_match, dedupe) decides which records reach the destination; watch (alert, monitor, budget, recurring, fees, anomaly, digest) notifies without changing data; AI (memory, ai_categorize, ai_merchant) fills what is still empty. rule/alert/memory take their own config field; every other type passes its block in `config`. Record-scoped types apply to feeds of the matching dataType (feedIds or allFeeds); memory, transfer_match and recurring are one per workspace, and the plan tier decides which types are available.
Parameters
namestringrequiredHuman-readable nametype"rule" | "alert" | "memory" | "lookup" | "monitor" | "filter" | "split" | "transfer_match" | "dedupe" | "ai_categorize" | "ai_merchant" | "budget" | "recurring" | "fees" | "anomaly" | "digest"requiredEnrichment type. Transform: rule, lookup. Guard: filter, split, transfer_match, dedupe. Watch: alert, monitor, budget, recurring, fees, anomaly, digest. AI: memory, ai_categorize, ai_merchant.dataType"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"Record shape this enrichment runs on (default: transactions where supported). Ignored for the workspace-scoped types (monitor, budget, fees, digest), which watch feeds and totals.enabledbooleanWhether the enrichment is active (default: true)feedIdsstring[]Feed IDs to apply to — get from list_feedsallFeedsbooleanApply to all feeds of the matching dataTyperuleConfigobjectRequired for type=rulealertConfigobjectRequired for type=alertmemoryConfigobjectOptional config for type=memoryconfigobjectThe type-specific config for every type OTHER than rule/alert/memory (which use their own fields above) — e.g. { matchField, matchMode, targetField, entries } for lookup, { conditions } for filter, { dimension, metric, window, threshold, currency, direction, destinations } for budget. Validated server-side against the type; see https://banksync.io/docs/enrichments/enrichments-overview for each type’s fields.
update_enrichmentwriteretry-safeUpdate an existing enrichment. Only include fields to change. The enrichment type cannot be changed. Use get_enrichment first to see the current config.
Parameters
enrichmentIdstringrequiredEnrichment ID — get from list_enrichmentsnamestringNew nameenabledbooleanEnable or disabledataType"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"Change the record shape (record-scoped types only; validated against the type)feedIdsstring[]Replace feed associationsallFeedsbooleanApply to all compatible feedsruleConfigobjectReplace rule configuration (rule type only)alertConfigobjectReplace alert configuration (alert type only)memoryConfigobjectReplace memory configuration (memory type only)configobjectReplace the type-specific config for any type other than rule/alert/memory. Validated against the stored type — call get_enrichment first to see the current block.
delete_enrichmentwritedo not retryPermanently delete an enrichment. This cannot be undone. Field memory patterns are preserved.
Parameters
enrichmentIdstringrequiredEnrichment ID — get from list_enrichments
Tables
Manage tables, schemas, rows, views, relations, comments, history, and files.
list_tablesread-onlyList the tables in the workspace (id, name, kind — typed or custom — resource type, fields, row count, schema/revision versions). Typed tables are bound to a resource contract (transactions, balances, holdings, …); custom tables are user-authored. Cursor-paginated.
Parameters
includeDeletedbooleanInclude tables in the trash (default: live tables only)cursorstringCursor from a previous pagelimitintegerPage size (default: server default)
get_tableread-onlyGet one table by id: its full field set (key, label, kind, display format, select options, whether the field is feed-owned and whether it can be written), the fields that form row identity, the promoted primary/date/amount bindings, row count, and its schema badge.
Parameters
tableIdstringrequiredTable ID — get from list_tables
create_tablewritedo not retryCreate a table in the workspace. A TYPED table names a resource type and derives its fields, row identity and promoted bindings from that contract — identical to the table a feed destination auto-creates, so a feed can write straight into it. A FREEFORM table declares its own fields and starts append-only (no row identity) so an unrelated re-import cannot overwrite real rows. Subject to the plan table limit.
Parameters
namestringrequiredHuman-readable table name. Names are NOT unique — a workspace may hold several tables called "Transactions". Address a table by its id, never by its name.iconstringOptional emoji shown beside the namekind"typed" | "freeform"requiredtyped = bound to a resource contract (fields are DERIVED from it, so the table matches what a feed destination would create); freeform = you declare the fields.resourceTypestringResource contract for a typed table (e.g. transactions, balances, holdings)fieldsobject[]Fields for a freeform table. Omit for a typed table — its fields are derived.retentionobjectRow retention, e.g. { mode: "forever" } or { mode: "days", days: 365 }. Rows older than the window are purged by the maintenance workflow, so `days` must be between 1 and 3650 — there is deliberately no "keep nothing" policy.
update_tablewriteretry-safeUpdate a table’s METADATA — name, icon, retention policy. Grid arrangement lives on views (see list_table_views / update_table_view), never on the table. Fields are deliberately not editable here: a schema change is a compare-and-set operation against a version this surface cannot hold safely, so field edits and typed→custom conversion belong to the table editor.
Parameters
tableIdstringrequiredTable ID — get from list_tablesnamestringNew nameiconstringNew emojiretentionobjectRow retention, e.g. { mode: "forever" } or { mode: "days", days: 365 }. Rows older than the window are purged by the maintenance workflow, so `days` must be between 1 and 3650 — there is deliberately no "keep nothing" policy.
delete_tablewritedo not retryMove a table to the trash. Rows are retained for the trash window and the table can be restored with restore_table; after the window expires the maintenance workflow purges it permanently.
Parameters
tableIdstringrequiredTable ID — get from list_tables
restore_tablewriteretry-safeRestore a table from the trash, with the rows it held when it was deleted. Fails once the trash window has expired and the purge has run.
Parameters
tableIdstringrequiredTable ID — use list_tables with includeDeleted to find it
query_table_rowsread-onlyRead rows from a table: a filter tree (and / or / not over field clauses, one hop through a relation allowed), text search, sort, a saved view, a keyset-paginated row page, group headers with counts, and calculations (sum, avg, count, min, max, …) over the whole filtered set — all answered over the SAME rows, so a total beside a page is a total of that page’s set. Each row comes back as typed properties keyed by field key: select options carry their labels, related rows and files come back as counted items, and a computed property carries a `stale` flag when a recomputation is outstanding. Pass rows: false for totals only. A question the schema cannot answer is refused with the offending path named.
Parameters
tableIdstringrequiredTable ID — get from list_tablesfilterunknownFilter tree. A clause is { field, op, value } where field is a field KEY (or one hop through a relation as "relation.field") and op is one of eq, neq, gt, gte, lt, lte, in, nin, contains, starts_with, ends_with, between, is_null, is_not_null. Combine with { logic: "and" | "or", conditions: [...] } or { logic: "not", condition }. A select field compares by option id; a date takes "YYYY-MM-DD", a range { start, end }, or a relative preset { rel: "this_month" }.searchstringCase-insensitive match over the primary field and text fieldsviewIdstringAnswer within a saved view: its filter is merged in and its sort used when none is givensortobject[]Sort order as [{ field, direction }] (default: the promoted date field, then row id)fieldsstring[]Field keys to return; omit for every fielddeletedbooleanRead the trash instead of live rowsrowsboolean | objectThe row page: { cursor?, limit? } (max 500), or false to skip rows and answer groups / calculations onlygroupsobjectGroup headers with counts and per-group calculations over the filtered setcalculationsobjectCalculations over the WHOLE filtered set by field key, e.g. { amount: "sum", date: "max" }. Values: count_all, count_values, count_empty, count_unique, percent_empty, percent_filled, sum, avg, median, min, max, range, earliest, latest, date_range.
insert_table_rowswritedo not retryWrite up to 500 rows into a table in one batch. Values are coerced against the field kinds (the whole batch is rejected if any cell cannot be read as its field’s kind, so a partial batch never lands), and rows whose identity matches a deleted row are skipped rather than resurrected. Subject to the plan row limit.
Parameters
tableIdstringrequiredTable ID — get from list_tablesrowsobject[]requiredRows keyed by FIELD KEY, e.g. { date: "2026-08-01", amount: 12.5, description: "…" }. A select value may be an option label or its id. Computed, automatic, relation and attachment fields are rejected, never ignored.mode"upsert" | "append"upsert (default) converges a re-sent batch onto the same rows via the table’s row identity; append writes unconditionally. A table with no identity fields behaves the same either way.mergeOnstring[]Field KEYS to converge on instead of the table’s row identity — your own key (an invoice number, an external id). A row matching one existing live row updates it; no match inserts; several matches are refused unless onMany is "first". A blank merge value never matches.onMany"fail" | "first"With mergeOn: what to do when a row matches several existing rows. Default fail.typecastbooleanDefault true: an option LABEL resolves to its id case-insensitively and a label no option carries is created. Pass false to have an unknown label refused instead.
update_table_rowwriteretry-safeChange one row’s cells. Supply only the fields you are changing — the rest keep their current values. Values are coerced against the field kinds, the row’s identity is recomputed from the MERGED row so a partial edit cannot give it a wrong identity, and anything computed from the edited cells is recomputed or marked for recomputation.
Parameters
tableIdstringrequiredTable ID — get from list_tablesrowIdstringrequiredRow ID — get from query_table_rowsvaluesobjectrequiredOnly the fields to change, keyed by field key. Others keep their current value.
delete_table_rowswritedo not retryMove rows to the trash. They stay restorable for the trash window, and a sync will NOT resurrect them in the meantime — a deleted row that arrives again from the bank is skipped, because the deletion was deliberate.
Parameters
tableIdstringrequiredTable ID — get from list_tablesrowIdsstring[]requiredRow IDs — get from query_table_rows
restore_table_rowswriteretry-safeBring soft-deleted rows back. Deleted rows stay restorable for 30 days — query them with query_table_rows and includeDeleted before restoring.
Parameters
tableIdstringrequiredTable IDrowIdsstring[]requiredRow ids to restore
aggregate_table_rowsread-onlyCompute sum / count / avg / min / max over a table field, optionally grouped by up to three fields and filtered — the fast way to answer "what did I spend on groceries last month" without paging through rows. Formula, rollup, lookup and count fields are materialised, so they aggregate exactly like a stored number. Money fields are grouped by currency unless you explicitly ask for a mixed-currency total.
Parameters
tableIdstringrequiredTable ID — get from list_tablesop"sum" | "count" | "avg" | "min" | "max"requiredAggregation to computefieldKeystringField key to aggregate. Required for everything except count.groupBystring[]Field keys to group by, e.g. ["category"] or ["accountName", "currency"]filtersobject[]Field filters, ANDed togetherlimitintegerMaximum groups returned (max 5000)mixedCurrenciesbooleanSet true to sum or average a money field ACROSS currencies. Without it, a money field is grouped by the table’s currency field so amounts in different currencies are never added together.
add_table_fieldwritedo not retryAdd one field to a table: any basic kind (text, number, date, select, status, checkbox, rating, user, attachment, auto number, JSON …), or a COMPUTED field — a formula (`source`) or a Lookup / Rollup / Count (`preset`), which are lowered to the same formula language. Formula fields are referenced by ID — `{fld:<fieldId>}` — never by label. A relation is created with create_table_relation because both sides are written together. Feed-owned fields on a typed table keep following their resource contract.
Parameters
tableIdstringrequiredTable ID — get from list_tableslabelstringrequiredHuman-readable field nameconfigobjectPer-kind configuration: { options: [{ id, label, color }] } for select/status, { maximum, icon } for rating, { multiple } for user, { prefix, digits } for auto number, { accept } for attachment.formatobjectDisplay format — { kind: "number", variant: "currency", currency: "AUD", precision: 2 }, { kind: "date", preset: "medium" }. Presentation only; it never rewrites a stored value.requiredbooleanReject a row that leaves this field blankdescriptionstringShown in the column tooltip and the APIiconstringIcon catalog id, e.g. emoji:storekind"text" | "long_text" | "number" | "checkbox" | "date" | "datetime" | "single_select" | "multi_select" | "status" | "url" | "email" | "phone" | "rating" | "user" | "auto_number" | "attachment" | "json" | "relation" | "formula" | "created_time" | "last_edited_time" | "created_by" | "last_edited_by"requiredWhat the field holds. Use create_table_relation for a relation — a relation is two fields created together, never one.sourcestringFormula source for kind "formula". Fields are referenced by ID in braces: `{fld:<fieldId>} * 1.1`, or across a relation `sum({fld:<relationFieldId>}.{fld:<targetFieldId>})`. Get the ids from get_table.presetobjectA structured computed field. Lowered to formula source server-side.positionintegerInsert here; appended when omittedexpectedSchemaVersionintegerAssert the table schema version you last read. Omit to let the call read it — the write still fails if someone changes the schema in between.
update_table_fieldwritedo not retryChange one field: its name, description, icon, display format, per-kind configuration (select options, rating maximum …), whether it is required, or a formula’s source/preset. Changing `kind` CONVERTS the stored values — preview it first. A field bound to a resource contract may be relabelled and reformatted but never retyped.
Parameters
tableIdstringrequiredTable IDfieldIdstringrequiredField ID — get from get_table (fields[].id)labelstringHuman-readable field nameconfigobjectPer-kind configuration: { options: [{ id, label, color }] } for select/status, { maximum, icon } for rating, { multiple } for user, { prefix, digits } for auto number, { accept } for attachment.formatobjectDisplay format — { kind: "number", variant: "currency", currency: "AUD", precision: 2 }, { kind: "date", preset: "medium" }. Presentation only; it never rewrites a stored value.requiredbooleanReject a row that leaves this field blankdescriptionstringShown in the column tooltip and the APIiconstringIcon catalog id, e.g. emoji:storekind"text" | "long_text" | "number" | "checkbox" | "date" | "datetime" | "single_select" | "multi_select" | "status" | "url" | "email" | "phone" | "rating" | "user" | "auto_number" | "attachment" | "json" | "relation" | "formula" | "created_time" | "last_edited_time" | "created_by" | "last_edited_by"Retype the field. Values that cannot be read as the new kind are cleared, so check first with preview_table_field_retype.sourcestringNew formula source (kind formula)presetobjectA structured computed field. Lowered to formula source server-side.expectedSchemaVersionintegerAssert the table schema version you last read. Omit to let the call read it — the write still fails if someone changes the schema in between.
delete_table_fieldwritedo not retryRemove a field from a table. The field is tombstoned and its key is never reused, so the values under it can never resurface under a later field with the same name. Refused for the primary field (designate another first) and for a field a formula or rollup still reads.
Parameters
tableIdstringrequiredTable IDfieldIdstringrequiredField ID — get from get_tableexpectedSchemaVersionintegerAssert the table schema version you last read. Omit to let the call read it — the write still fails if someone changes the schema in between.
move_table_fieldwriteretry-safeMove a field to a new position. Position is the column ORDER a person sees; it changes no stored value and no field key.
Parameters
tableIdstringrequiredTable IDfieldIdstringrequiredField IDpositionintegerrequiredZero-based target positionexpectedSchemaVersionintegerAssert the table schema version you last read. Omit to let the call read it — the write still fails if someone changes the schema in between.
set_table_primary_fieldwriteretry-safeDesignate which field NAMES a row: what a relation chip shows, what a relation picker searches, and what appears wherever a row is referred to. Must be a text-like field.
Parameters
tableIdstringrequiredTable IDfieldIdstringrequiredField ID to promoteexpectedSchemaVersionintegerAssert the table schema version you last read. Omit to let the call read it — the write still fails if someone changes the schema in between.
preview_table_field_retyperead-onlyDry-run a field type change: how many values the column holds and how many of them cannot be read as the new kind (those are CLEARED by the retype, not converted). Read this before update_table_field changes a `kind` — the conversion is a data change, and "127 of 5,204 values will be cleared" is the sentence a user needs before approving it.
Parameters
tableIdstringrequiredTable IDfieldIdstringrequiredField ID — get from get_tablekind"text" | "long_text" | "number" | "checkbox" | "date" | "datetime" | "single_select" | "multi_select" | "status" | "url" | "email" | "phone" | "rating" | "user" | "auto_number" | "attachment" | "json" | "relation" | "formula" | "created_time" | "last_edited_time" | "created_by" | "last_edited_by"requiredThe kind it would becomeconfigobjectConfiguration the new kind would carry (select options, rating maximum …)
create_table_relationwritedo not retryLink two tables. Creates the relation field here and, when `reciprocal` is given, the reverse field on the target — one operation, because a half-created relation is a column pointing at nothing. Once it exists, link rows with link_table_rows and summarise across it with a Lookup / Rollup / Count field (add_table_field).
Parameters
tableIdstringrequiredTable the relation is added totargetTableIdstringrequiredTable it points at — the same table for a self-relationlabelstringrequiredField name on this tablecardinality"one" | "many"one = a row links to at most one target row; many = severalreciprocalobjectCreate the reverse field on the target table too. Omit for a one-way relation.previewFieldIdsstring[]Target field ids shown beside the primary value on a chipdescriptionstringInput for description.iconstringInput for icon.expectedSchemaVersionintegerAssert the table schema version you last read. Omit to let the call read it — the write still fails if someone changes the schema in between.
link_table_rowswritedo not retrySet, add to, remove from, or reorder the rows a relation cell points at. Links are membership, not a cell value, which is why update_table_row refuses a relation key. Cardinality and the per-cell cap are enforced by the write itself, so an over-link is refused rather than silently truncated. Rollups and lookups reading this relation are invalidated on BOTH tables.
Parameters
tableIdstringrequiredTable holding the row being editedrowIdstringrequiredRow whose relation cell changesfieldKeystringrequiredRelation field KEY on that tableop"add" | "remove" | "set" | "reorder"requiredadd = link these as well; remove = unlink these; set = make the cell exactly this list (unlinking anything absent); reorder = keep the same links in this ordertargetRowIdsstring[]requiredRow ids in the TARGET table
search_relation_targetsread-onlyFind candidate rows in the table a relation points at, matched on the target’s primary (naming) field. This is how you turn "link the Acme invoice" into the row id link_table_rows needs.
Parameters
tableIdstringrequiredTable holding the relation fieldfieldKeystringrequiredRelation field KEYquerystringText to match against the target’s namelimitintegerInput for limit.
validate_table_formularead-onlyCompile a formula (or a Lookup / Rollup / Count preset) against the workspace’s real schema WITHOUT saving it: reports whether it parses, what type it returns, which fields it depends on, how many relation hops it makes, and whether it is volatile (time-dependent). An invalid draft is an ANSWER here, not an error — use it before add_table_field.
Parameters
tableIdstringrequiredTable the formula belongs tosourcestringFormula source to check — fields are `{fld:<fieldId>}`, from get_tablepresetobjectStructured preset to check insteadfieldIdstringThe field being edited, when checking a change to an existing formulasampleRowsintegerRows to evaluate it against (default 3, 0 = compile only)
list_table_activityread-onlyThe audit trail: who created, edited, deleted, restored or purged rows, and which feed sync wrote a batch. Filter to one row to answer "what happened to this record". Cursor-paginated, newest first.
Parameters
tableIdstringrequiredTable IDrowIdstringOnly this row’s historyevent"insert" | "update" | "delete" | "restore" | "purge" | "sync_batch"Only this kind of eventlimitintegerInput for limit.cursorstringFrom a previous page
list_table_viewsread-onlyThe table’s saved views — shared ones plus the caller’s own personal views — in sibling order, with each view’s query (filter, sort, group), field arrangement, footer calculations, row colours, layout and version. Pass a view id to query_table_rows to read rows as that view shows them.
Parameters
tableIdstringrequiredTable ID
create_table_viewwritedo not retrySave an arrangement of a table: its filter, sort and grouping, which fields show and how wide, footer calculations, row colour rules, and a layout (grid, board, calendar, gallery, timeline, list, form). A shared view is structure and is refused while the table is locked; a personal view belongs to the acting user and is never shown to anyone else.
Parameters
tableIdstringrequiredTable IDnamestringrequiredView name, unique per tablescope"shared" | "personal"shared (default) is visible to the whole workspace; personal is the acting user’s aloneduplicateOfstringCopy everything but the name from this view, its layout includediconstringIcon catalog id (e.g. emoji:pin); omit for the layout defaultqueryobjectWhich rows, in what order, grouped how: { filter, sort: [{ field, direction }], group: [{ field }] }. filter is the same tree query_table_rows takes, or null.fieldsobjectField arrangement by key: { order, hidden, widths, pins: { left, right }, wrap }calculationsobjectFooter calculation per field key, e.g. { amount: "sum" }decorationsobject | object[]Row colour rules: [{ id, condition, color, style? }]; the first match winsdensity"comfortable" | "compact" | "tight"Input for density.layoutunknownLayout: { layout: "grid" }, { layout: "board", groupField }, { layout: "calendar", dateField }, { layout: "gallery" }, { layout: "timeline", startField }, { layout: "list" } or { layout: "form" }. A board groups by a select, status, single user or one-row relation field.
update_table_viewwritedo not retryChange a saved view’s name, icon, query (filter / sort / group), field arrangement, footer calculations, row colours, density or layout. Only the properties you pass change. A compare-and-set on the view version: another editor’s change is reported rather than overwritten. Another user’s personal view is not found.
Parameters
tableIdstringrequiredTable IDviewIdstringrequiredView ID — from list_table_viewsnamestringInput for name.expectedVersionintegerThe view version you read. Omit to edit the version this call reads — pass it when a human is editing the same view.iconstring | nullIcon catalog id; null clears itqueryobjectWhich rows, in what order, grouped how: { filter, sort: [{ field, direction }], group: [{ field }] }. filter is the same tree query_table_rows takes, or null.fieldsobjectField arrangement by key: { order, hidden, widths, pins: { left, right }, wrap }calculationsobjectFooter calculation per field key, e.g. { amount: "sum" }decorationsobject | object[]Row colour rules: [{ id, condition, color, style? }]; the first match winsdensity"comfortable" | "compact" | "tight"Input for density.layoutunknownLayout: { layout: "grid" }, { layout: "board", groupField }, { layout: "calendar", dateField }, { layout: "gallery" }, { layout: "timeline", startField }, { layout: "list" } or { layout: "form" }. A board groups by a select, status, single user or one-row relation field.
delete_table_viewwriteretry-safeRemove a saved view. Rows are untouched. A table keeps at least one shared view, so the last shared view is refused; a personal view can always be removed by its owner.
Parameters
tableIdstringrequiredTable IDviewIdstringrequiredView ID — from list_table_views
list_row_commentsread-onlyDeprecated: use list_row_messages (tables.messages.list) instead. This alias stops working on 2026-12-15. The comments on one row, oldest first: author, body, when it was edited, and whether and by whom it was resolved. Cursor-paginated. A row in the trash keeps its comments.
Parameters
tableIdstringrequiredTable IDrowIdstringrequiredRow ID — get from query_table_rowslimitintegerInput for limit.cursorstringFrom a previous page
add_row_commentwritedo not retryDeprecated: use post_row_message (tables.messages.create) instead. This alias stops working on 2026-12-15. Leave a note on one row. A comment is not a cell: it changes no value, and it appears in the row’s history authored by the acting user.
Parameters
tableIdstringrequiredTable IDrowIdstringrequiredRow ID — get from query_table_rowsbodystringrequiredThe comment, plain text
resolve_row_commentwriteretry-safeDeprecated: use resolve_row_message (tables.messages.resolve) instead. This alias stops working on 2026-12-15. Close a comment thread (resolved: true, the default) or reopen it (resolved: false) without erasing it. The comment records who resolved it.
Parameters
tableIdstringrequiredTable IDcommentIdstringrequiredComment ID — from list_row_commentsresolvedbooleantrue (default) resolves; false reopens
list_row_messagesread-onlyThe conversation on one row, oldest first: author, body, when it was edited, whether and by whom it was resolved, and the thread fields role, audience and publishedAt. Cursor-paginated. Only published messages to the row’s participants are ever returned. A row in the trash keeps its messages.
Parameters
tableIdstringrequiredTable IDrowIdstringrequiredRow ID — get from query_table_rowslimitintegerInput for limit.cursorstringFrom a previous page
post_row_messagewritedo not retryWrite a message on one row’s thread. A message is not a cell: it changes no value, and it appears in the row’s history as a `message` event authored by the acting user. A mentioned member is told in their notification tray. Internal notes cannot be written on BankSync tables.
Parameters
tableIdstringrequiredTable IDrowIdstringrequiredRow ID — get from query_table_rowsbodystringrequiredThe message, plain text. Mention a workspace member with an @person:user:<userId> token (user ids from list_workspace_members); a mention of anyone who is not an active member of this workspace is refused.idempotencyKeystringSend the same key again and the message is not written twiceattachmentIdsstring[]Files staged with POST /v1/tables/{tableId}/attachments (no fieldKey) to send with the message, at most 10. An inline image in the body () must name one of them.
edit_row_messagewriteretry-safeChange the wording of a message you wrote, within 30 minutes of posting it. The previous wording is kept in the message’s history.
Parameters
tableIdstringrequiredTable IDmessageIdstringrequiredMessage ID — from list_row_messagesbodystringrequiredThe message, plain text. Mention a workspace member with an @person:user:<userId> token (user ids from list_workspace_members); a mention of anyone who is not an active member of this workspace is refused.
delete_row_messagewriteretry-safeRemove a message from a row’s thread. Only its author or a workspace admin may; restore_row_message brings it back within the trash window. The row is untouched.
Parameters
tableIdstringrequiredTable IDmessageIdstringrequiredMessage ID — from list_row_messages
restore_row_messagewriteretry-safeBring back a message deleted from a row’s thread, within the trash window. Only its author or a workspace admin may.
Parameters
tableIdstringrequiredTable IDmessageIdstringrequiredThe deleted message’s ID
resolve_row_messagewriteretry-safeClose a message’s conversation (resolved: true, the default) or reopen it (resolved: false) without erasing it. The message records who resolved it.
Parameters
tableIdstringrequiredTable IDmessageIdstringrequiredMessage ID — from list_row_messagesresolvedbooleantrue (default) resolves; false reopens
react_to_rowwriteretry-safeLeave (on: true, the default) or take back (on: false) a reaction of a kind the table admits. A kind the table does not admit is refused by name; BankSync’s tables admit none today.
Parameters
tableIdstringrequiredTable IDrowIdstringrequiredRow ID — get from query_table_rowskindstringrequiredThe reaction kind, e.g. voteonbooleantrue (default) reacts; false removes the reaction
list_row_historyread-onlyEvery event that touched one row — edits with the changed cells (previous and next value), comments, deletion and restore — newest first, cursor-paginated. Answers "what happened to this record"; a row in the trash keeps its history. list_table_activity is the table-wide trail.
Parameters
tableIdstringrequiredTable IDrowIdstringrequiredRow ID — get from query_table_rowslimitintegerInput for limit.cursorstringFrom a previous page
list_row_attachmentsread-onlyThe files attached to a row: id, filename, content type, size and order. Bytes are not returned — an attachment is fetched over HTTP from the API, which is also the only surface that can upload one, because a file is bytes rather than a tool argument.
Parameters
tableIdstringrequiredTable IDrowIdstringrequiredRow ID
delete_table_attachmentwriteretry-safeRemove one attached file. The cell entry, the index row and the stored object are destroyed together, and the workspace’s storage total is credited back.
Parameters
tableIdstringrequiredTable IDassetIdstringrequiredAttachment id — from list_row_attachments
Dashboards
Create, inspect, and update dashboards and their widgets.
list_foldersread-onlyList folders in the workspace. Optionally scoped to a parent folder.
Parameters
parentFolderIdstring | nullParent folder id. Omit for top level.
get_folderread-onlyGet one folder by id.
Parameters
folderIdstringrequiredInput for folderId.
create_folderwritedo not retryCreate a folder to organize dashboards, widgets, and queries.
Parameters
namestringrequiredInput for name.parentFolderIdstring | nullInput for parentFolderId.iconstringInput for icon.colorstringInput for color.
update_folderwriteretry-safePartially update a folder (name, icon, color, order, parent).
Parameters
folderIdstringrequiredInput for folderId.patchobjectrequiredInput for patch.
delete_folderwriteretry-safeDelete a folder. Non-recursive lifts children to the parent and unfiles dashboards; recursive deletes the whole subtree.
Parameters
folderIdstringrequiredInput for folderId.recursivebooleanInput for recursive.
move_folderwriteretry-safeMove a folder under a new parent (null = top level). Cycles are rejected.
Parameters
folderIdstringrequiredInput for folderId.newParentFolderIdstring | nullrequirednull = top level
list_queriesread-onlyList queries in the workspace (id, slug, name, source). Note: dataType filter is accepted but not honored by the store.
Parameters
dataType"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"Input for dataType.feedIdstringInput for feedId.tagstringInput for tag.
search_queriesread-onlySearch queries by name/slug.
Parameters
qstringrequiredInput for q.limitintegerInput for limit.
get_queryread-onlyGet one query by id.
Parameters
queryIdstringrequiredInput for queryId.
create_querywritedo not retryCreate a single-feed query. transforms is the validated discriminated-union pipeline.
Parameters
slugstringrequiredLowercase kebab-case slug, 3-64 charsnamestringrequiredInput for name.descriptionstringInput for description.feedIdstringrequiredUse list_feeds first.dataType"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"requiredInput for dataType.scope"workspace" | "family"Family scope reads across portal children.parametersobject[]Input for parameters.transformsunknown[]Input for transforms.folderIdstring | nullInput for folderId.tagsstring[]Input for tags.
update_querywriteretry-safePartially update a query. Supply version for optimistic concurrency (409 on mismatch). The store enforces DAG acyclicity for derived queries.
Parameters
queryIdstringrequiredInput for queryId.versionintegerInput for version.patchobjectrequiredInput for patch.
delete_querywriteretry-safeDelete a query. Fails if any widget still references it.
Parameters
queryIdstringrequiredInput for queryId.
preview_queryread-onlyRun a query against the current snapshot and return up to 5000 rows. Unavailable in environments without a render plane wired.
Parameters
queryIdstringrequiredInput for queryId.paramsobjectInput for params.
create_multi_feed_querywritedo not retryCreate a query that concatenates or joins two or more feeds.
Parameters
slugstringrequiredLowercase kebab-case slug, 3-64 charsnamestringrequiredInput for name.descriptionstringInput for description.sourcesobject[]requiredTwo or more feed sources to combine.combineunknownrequiredInput for combine.aliasesstring[]Input for aliases.transformsunknown[]Input for transforms.folderIdstring | nullInput for folderId.tagsstring[]Input for tags.
create_static_querywritedo not retryCreate a query backed by a literal in-line rowset.
Parameters
slugstringrequiredLowercase kebab-case slug, 3-64 charsnamestringrequiredInput for name.descriptionstringInput for description.rowsobject[]requiredLiteral rowset.typesobjectInput for types.transformsunknown[]Input for transforms.folderIdstring | nullInput for folderId.tagsstring[]Input for tags.
create_query_derived_querywritedo not retryCreate a query derived from an upstream query (transform chain). Acyclicity + max depth 8 enforced by the store.
Parameters
slugstringrequiredLowercase kebab-case slug, 3-64 charsnamestringrequiredInput for name.descriptionstringInput for description.upstreamQueryIdstringrequiredInput for upstreamQueryId.transformsunknown[]Input for transforms.folderIdstring | nullInput for folderId.tagsstring[]Input for tags.
list_widgetsread-onlyList widgets in the workspace (id, slug, name, shape). Private widgets are filtered.
Parameters
shape"view" | "control"Input for shape.tagstringInput for tag.
search_widgetsread-onlySearch widgets by name/tags.
Parameters
qstringrequiredInput for q.limitintegerInput for limit.
get_widgetread-onlyGet one widget by id (full spec).
Parameters
widgetIdstringrequiredInput for widgetId.
create_widgetwritedo not retryCreate a view or control widget. Use describe_widget_kind / validate_widget_spec to shape view/control. Client-plane filters go in clientPipeline (widget_pipeline_append), not here.
Parameters
slugstringrequiredLowercase kebab-case slug, 3-64 charsnamestringrequiredInput for name.descriptionstringInput for description.shape"view" | "control"requiredInput for shape.viewobjectRequired when shape="view". The view spec, discriminated by `kind`. The full per-kind field schema is NOT inlined here because it is very large: call list_widget_kinds for the available kinds, describe_widget_kind for one kind's fields, and validate_widget_spec to check a draft. The spec is validated in full server-side, so an invalid one is refused, not silently accepted.controlobjectRequired when shape="control". The control spec, discriminated by `kind`. Not inlined here: call list_widget_kinds({category:"control"}) for the kinds, describe_widget_kind for one kind's fields, and describe_actions / validate_action for the action specs a control emits. Validated in full server-side.queryIdstring | nullInput for queryId.queryBindingsobjectInput for queryBindings.visibility"private" | "workspace" | "public-link" | "public-indexed"Input for visibility.folderIdstring | nullInput for folderId.tagsstring[]Input for tags.
update_widgetwriteretry-safePartially update a widget. Supply version for optimistic concurrency (409 on mismatch). The store validates shape.
Parameters
widgetIdstringrequiredInput for widgetId.versionintegerInput for version.patchobjectrequiredInput for patch.
delete_widgetwriteretry-safeDelete a widget. Fails if it is pinned to any dashboard.
Parameters
widgetIdstringrequiredInput for widgetId.
publish_widgetwritedo not retrySet a widget's visibility / publishing options and mint or rotate its shortcode.
Parameters
visibility"private" | "workspace" | "public-link" | "public-indexed"requiredInput for visibility.indexablebooleanInput for indexable.embedAllowedOriginsstring[]Input for embedAllowedOrigins.regeneratebooleanInput for regenerate.widgetIdstringrequiredInput for widgetId.
list_dashboardsread-onlyList dashboards in the workspace (id, slug, name). Private dashboards are filtered. Staged drafts (status "draft", e.g. mid-composition) are hidden unless includeDrafts is true.
Parameters
includeDraftsbooleanInclude staged draft dashboards (hidden by default).
get_dashboardread-onlyGet one dashboard by id or slug (full layout + widgets + filters).
Parameters
dashboardIdstringrequiredInput for dashboardId.
create_dashboardwritedo not retryCreate an empty dashboard. Pin widgets with attach_widget. For a full dashboard (widgets + layout) prefer compose_dashboard — one atomic call, no orphaned steps.
Parameters
slugstringrequiredLowercase kebab-case slug, 3-64 charsnamestringrequiredInput for name.descriptionstringInput for description.folderIdstring | nullInput for folderId.iconstringInput for icon.visibility"private" | "workspace" | "public-link" | "public-indexed"Input for visibility.filtersunknown[]Initial DashboardFilter[]
attach_widgetwritedo not retryPin an existing widget onto a dashboard (appends a placement + grid cell). The grid is 12 columns: KPIs read best 3 wide, charts 6 wide (pairs), tables 12 wide. Without an explicit layout the cell is appended below existing rows at column 0 — pass layout to compose a real grid, or build the whole dashboard with compose_dashboard instead.
Parameters
dashboardIdstringrequiredInput for dashboardId.widgetIdstringrequiredInput for widgetId.size"s" | "m" | "l" | "full"s | m | l | fulllayoutobjectExplicit grid cell; otherwise appended below existing rows.
set_dashboard_filterwriteretry-safeAdd or replace a global dashboard filter (upsert by filter.id).
Parameters
dashboardIdstringrequiredInput for dashboardId.filterobjectrequiredInput for filter.
publish_dashboardwritedo not retrySet a dashboard's visibility / publishing options and mint or rotate its shortcode.
Parameters
visibility"private" | "workspace" | "public-link" | "public-indexed"requiredInput for visibility.indexablebooleanInput for indexable.embedAllowedOriginsstring[]Input for embedAllowedOrigins.regeneratebooleanInput for regenerate.dashboardIdstringrequiredInput for dashboardId.
update_dashboardwriteretry-safeUpdate dashboard metadata, layout, filters, or theme. Layout and filter changes require the current version.
Parameters
dashboardIdstringrequiredInput for dashboardId.versionintegerInput for version.patchobjectrequiredInput for patch.
delete_dashboardwriteretry-safeDelete a dashboard. Widgets pinned only here become unpinned but remain in the library.
Parameters
dashboardIdstringrequiredInput for dashboardId.
compose_dashboardwritedo not retryCreate queries + widgets + a dashboard ATOMICALLY in one call, with a real 12-column grid layout (auto-packed unless you pass one: KPI row on top, charts in half-width pairs, tables full-width). Pass ONE of: widgets (create_widget-shaped specs), fromFeed (feedId + dataType), or fromBank (bankId + dataType) — fromBank builds the curated starter directly from a connected bank with NO feed. WITH `widgets`, if the widgets read data you MUST also pass `dataSource` (bankId or feedId + dataType) to bind them, or they render "No data source". PREFER THIS over the create_dashboard → create_widget → attach_widget sequence: one call cannot leave orphaned library widgets or a single-column stack. Returns the dashboard id, slug, path, and created widget ids.
Parameters
operationIdstringInput for operationId.namestringrequiredInput for name.descriptionstringInput for description.iconstringInput for icon.folderIdstring | nullInput for folderId.visibility"private" | "workspace" | "public-link" | "public-indexed"Default "workspace".widgetsobject[]Widgets to create + pin. Provide either widgets OR fromFeed.setupWidgetsobject[]Design widget placements that stay in setup until a data source is chosen.fromFeedobjectAlternative to widgets: build the curated starter dashboard for this feed (KPI row + signature charts + records table).fromBankobjectAlternative to widgets/fromFeed: build the curated starter dashboard reading a CONNECTED BANK directly (no feed needed — the render plane reads the bank live). Use this when the user has a bank connected but no feed for the data type.dataSourceobjectWith `widgets`: BIND every data-driven widget (chart/table/kpi) to this source, so the dashboard has real data. REQUIRED for a custom `widgets` dashboard whose widgets read data — otherwise they render "No data source". Pass exactly one of bankId (a connected bank — no feed needed) or feedId, plus the dataType. Resolve it from list_buildable_insights (prefer a live bank source). Ignored for fromBank/fromFeed (those bind their own source).layoutobject[]Explicit 12-col grid cells matched to widgets by index. Omit to auto-pack (KPI row on top, charts in half-width pairs, tables full-width).filtersunknown[]Initial DashboardFilter[]. A pinned date_range filter is added when absent.draftbooleanCreate as a staged DRAFT: hidden from the dashboards list until committed via commit_dashboard_draft (renderable via direct link meanwhile).
commit_dashboard_draftwriteretry-safeFlip a draft dashboard (e.g. one composed with compose_dashboard draft:true) to active so it appears in the dashboards list and sidebar. Idempotent — committing an already active dashboard is a no-op.
Parameters
dashboardIdstringrequiredInput for dashboardId.acknowledgeSetupbooleanInput for acknowledgeSetup.
list_widget_kindsread-onlyList the available (non-deprecated) widget kinds, optionally filtered by category.
Parameters
category"chart" | "table" | "kpi" | "control" | "layout" | "custom"Input for category.
describe_widget_kindread-onlyGet the full catalog entry for one widget kind: its complete field schema (JSON Schema), default spec, worked examples, required columns and allowed sizes. This is where the per-kind fields live, since create_widget / update_widget / compose_dashboard advertise the view and control specs as opaque objects.
Parameters
kindstringrequiredInput for kind.
describe_actionsread-onlyDescribe the available widget action kinds (or one kind) with a minimal default spec.
Parameters
kindstringInput for kind.
validate_actionread-onlyDry-run validate an action spec. Pure validation — no mutation.
Parameters
actionobjectAction spec; discriminated by action.kind
validate_widget_specread-onlyDry-run validate a widget view/control spec against a kind's schema. No mutation.
Parameters
kindstringrequiredInput for kind.specobjectWidget view/control spec
create_table_saved_viewwritedo not retryAppend a saved view (sort/filter/column state) to a table widget.
Parameters
widgetIdstringrequiredInput for widgetId.namestringrequiredInput for name.stateobjectrequiredTableViewStateisDefaultbooleanInput for isDefault.scope"user" | "workspace"Input for scope.
widget_pipeline_appendwritedo not retryAppend a client-pipeline op to a view widget.
Parameters
widgetIdstringrequiredInput for widgetId.opobjectrequiredClient-pipeline op; discriminated by op.kind
widget_pipeline_removewritedo not retryRemove the client-pipeline op at the given index.
Parameters
widgetIdstringrequiredInput for widgetId.indexintegerrequiredInput for index.
widget_pipeline_movewriteretry-safeReorder a client-pipeline op from one index to another (post-removal destination index).
Parameters
widgetIdstringrequiredInput for widgetId.fromintegerrequiredInput for from.tointegerrequiredInput for to.
describe_fieldsread-onlyDescribe the field catalog reachable from a feed data type, following relationship join hops.
Parameters
feedDataType"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"requiredInput for feedDataType.depthLimitintegerMax join hops. Default 3.
suggest_widgetsread-onlySuggest widget candidates for a feed data type, ranked by relevance. feedId is REQUIRED (each candidate query binds to it) — call list_feeds to pick one.
Parameters
feedDataType"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"requiredInput for feedDataType.feedIdstringrequiredThe feed the candidate queries bind to. Required — see list_feeds.limitintegerInput for limit.aggregatedbooleanInput for aggregated.
list_relationshipsread-onlyList the workspace feed relationships. Falls back to built-ins when no custom registry is wired.
No parameters.
create_relationshipwritedo not retryRegister a custom feed relationship (join edge in the semantic layer).
Parameters
fromFeedDataType"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"requiredInput for fromFeedDataType.fromFieldstringrequiredInput for fromField.toFeedDataType"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"requiredInput for toFeedDataType.toFieldstringrequiredInput for toField.cardinality"one-to-one" | "many-to-one" | "one-to-many"requiredInput for cardinality.labelstringInput for label.
delete_relationshipwriteretry-safeDelete a custom feed relationship. Built-ins are protected.
Parameters
relationshipIdstringrequiredInput for relationshipId.
resolve_field_bindingread-onlyDry-run a field-to-channel binding for a widget. Does NOT mutate — bind_widget_field ships in a future phase.
Parameters
widgetIdstringrequiredInput for widgetId.channelstringrequired'x','y','value','source','target','category', …fieldRefobjectrequiredInput for fieldRef.
Custom Widgets
Create and inspect custom dashboard widgets.
generate_custom_widgetread-onlyESCAPE HATCH — use this ONLY when a native widget spec (propose_insight_widget / author_widget) genuinely cannot express the UI; a validated native kind is always preferred (it renders in-process, no sandbox cost). When you do generate: write a React component in TS/JSX. Style with TAILWIND utility classes (just `className="..."` — the runtime JITs them, no config) and shadcn/ui-style components built on the vendored Radix primitives. Import ONLY: `@banksync/widget-sdk` (defineWidget, useData, useDataset, useSettings, useSetting, useFilters, useTheme, brokerFetch, emitFilter, openUrl), React, `recharts` (charts), `lucide-react` (icons), and the vendored libs (clsx, tailwind-merge, class-variance-authority, @radix-ui/react-*, d3-*, date-fns, zod). DESIGN — the widget must look BEAUTIFUL and FILL its dashboard tile (the user sizes the cell; the widget fills it responsively, like a chart). Give the ROOT `className="flex h-full flex-col gap-4 p-5 ..."`. Use the THEME-AWARE shadcn semantic colors (bg-card, text-card-foreground, text-muted-foreground, border, bg-primary/10, text-primary) so it adapts to light/dark automatically — avoid hard-coded black/white; give it a clear header (an icon in a rounded bg-primary/10 tile + a title), generous padding/gap, and rounded corners. For charts use recharts in a `<ResponsiveContainer width="100%" height="100%">` inside a `min-h-0 flex-1` box so the chart GROWS to fill; prefer a donut (innerRadius) with the total centered in the hole, rounded slices, and a legend with values + %. NO network (fetch), storage (localStorage), or frame access — those are rejected with correctable, line-anchored errors (fix and retry, max 2). Read the widget-SDK docs (search_docs) for the API + examples. Supply a complete portable manifest declaring every named input, widget-local field, setting, capability, and allowed network origin. Use `useDataset(inputId)` for data and `useSettings()`/`useSetting()` for configuration; `useData()` is legacy-v1 only. The source is compiled to a hash-pinned sandboxed build and returned as an INERT package proposal — safe to call speculatively. It does not create an installation, choose a bank/feed, or grant access; the user continues through the normal configure and approval flow.
Parameters
sourcestringrequiredInput for source.manifestobjectrequiredInput for manifest.bundleIdstringInput for bundleId.
Templates
Discover and apply reusable feed and dashboard templates.
list_templatesread-onlyList dashboard/widget templates visible to the workspace (builtin + workspace + public), with optional facets.
Parameters
namespaces"builtin" | "workspace" | "community"[]Input for namespaces.grain"widget" | "dashboard"Input for grain.category"personal-finance" | "budgeting" | "cash-flow" | "net-worth" | "spending" | "income" | "investing" | "portfolio" | "loans-debt" | "savings-goals" | "business-finance" | "tax" | "subscriptions" | "forecasting" | "other"Input for category.tags"overview" | "deep-dive" | "monitoring" | "reconciliation" | "planning" | "daily" | "weekly" | "monthly" | "quarterly" | "annual" | "consumer" | "prosumer" | "smb" | "advisor" | "single-metric" | "comparison" | "trend" | "breakdown" | "table-heavy"[]Input for tags.limitintegerInput for limit.
describe_templateread-onlyRead a template: its binding manifest (slots/required fields/filters) and portable spec body.
Parameters
idOrKeystringrequiredTemplate id or key.
check_template_preconditionsread-onlyCheck whether a workspace can instantiate a template, given which feed types it has. Returns the gaps (missing feed/field/relationship).
Parameters
idOrKeystringrequiredInput for idOrKey.availableFeedTypes"transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"[]requiredFeed data types present in the workspace.
instantiate_templatewritedo not retryInstantiate a template into concrete dashboard/widget entities for the workspace. Resource slots take a typed local feed or bank binding.
Parameters
idOrKeystringrequiredInput for idOrKey.slotsobjectslotId → local feed id or typed feed/bank binding.paramValuesobjectInput for paramValues.
publish_templatewriteretry-safePublish a workspace template. Runs BOTH gates (portability + safety); blocked publishes return the violation list. Mints/rotates a share shortcode.
Parameters
idstringrequiredInput for id.visibility"private" | "workspace" | "public-link" | "public-indexed"requiredInput for visibility.indexablebooleanInput for indexable.embedAllowedOriginsstring[]Input for embedAllowedOrigins.regeneratebooleanInput for regenerate.category"personal-finance" | "budgeting" | "cash-flow" | "net-worth" | "spending" | "income" | "investing" | "portfolio" | "loans-debt" | "savings-goals" | "business-finance" | "tax" | "subscriptions" | "forecasting" | "other"Input for category.tags"overview" | "deep-dive" | "monitoring" | "reconciliation" | "planning" | "daily" | "weekly" | "monthly" | "quarterly" | "annual" | "consumer" | "prosumer" | "smb" | "advisor" | "single-metric" | "comparison" | "trend" | "breakdown" | "table-heavy"[]Input for tags.
SimpleFIN
Manage SimpleFIN access and connections.
list_simplefin_accessread-onlyList the SimpleFIN access this workspace has issued to third-party apps (Actual Budget, Securo, and other SimpleFIN clients). Returns each access's name, target app, status (waiting for the app to claim its setup token / active / expired / revoked), account scope, and when an app last used it. Setup tokens and secrets are never returned.
No parameters.
create_simplefin_accesswritedo not retryIssue a new SimpleFIN setup token so a third-party app can read this workspace’s banks. The setup token is returned ONCE and cannot be retrieved again — surface it to the user immediately. Optionally limit the access to specific accountIds; omit to share every account, including banks connected later.
Parameters
namestringrequiredLabel for this access, e.g. "Actual Budget — laptop"app"actual" | "securo" | "other"Which client this is for; tailors the setup instructions onlyaccountIdsstring[]Limit the access to these accounts — get ids from list_accountsincludePendingDefaultbooleanInclude pending transactions when the app does not ask explicitly
rotate_simplefin_accesswritedo not retryIssue a fresh setup token for an existing SimpleFIN access. The credentials the app currently holds stop working immediately, so the user must paste the new token before it syncs again. Use when a token may have leaked, or to re-link an app.
Parameters
accessIdstringrequiredSimpleFIN access id — get from list_simplefin_access
revoke_simplefin_accesswriteretry-safeRevoke a SimpleFIN access. The app receives an error on its next sync and cannot be re-enabled — create new access instead. The record is kept so the revocation is visible.
Parameters
accessIdstringrequiredSimpleFIN access id — get from list_simplefin_access