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-only

Search 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

  • query stringrequiredKeywords or a natural-language question about using BankSync.
read_docread-only

Read 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

  • slug stringrequiredThe doc slug from a search_docs result, e.g. "australian-banks".

Workspace

Manage workspaces and connected destination integrations.

list_workspacesread-only

List 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-only

Get name, subscription plan, and settings for the current workspace. Returns 403 if the user is not a member.

No parameters.

list_integrationsread-only

List 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-safe

Remove 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

  • integrationId stringrequiredIntegration 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-only

List 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-only

Get 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 retry

Create a new client portal under this parent workspace. Optionally invite the client by email. Owner/admin only.

Parameters

  • name stringrequiredClient business name for the portal
  • description stringInput for description.
  • icon stringOptional emoji icon
  • clientEmail stringIf set, sends an invitation to the client
  • clientRole "editor" | "viewer"Invited client role — default editor
  • capabilities objectPer-portal member capabilities. Default: client CAN connect banks, CANNOT create feeds or integrations.
  • limits objectPer-portal caps. null = no cap beyond the family ceiling.
update_portalwriteretry-safe

Partially update a client portal (name, description, icon, member capabilities, per-portal limits). Owner/admin only.

Parameters

  • portalId stringrequiredPortal ID — get from list_portals
  • name stringInput for name.
  • description string | nullnull clears the description
  • icon string | nullnull clears the icon
  • capabilities objectPer-portal member capabilities. Default: client CAN connect banks, CANNOT create feeds or integrations.
  • limits objectPer-portal caps. null = no cap beyond the family ceiling.
delete_portalwriteretry-safe

Soft-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

  • portalId stringrequiredPortal ID to delete — get from list_portals

Bank Connections

Connect, list, and manage bank/institution connections.

list_banksread-only

List 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-only

Get 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

  • bankId stringrequiredBank connection ID — get from list_banks
create_bank_linkwritedo not retry

Initiate 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

  • providerInstanceId stringrequiredCanonical 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.
  • email stringUser email address — required for SnapTrade and SaltEdge workspace registration. Not needed for Plaid.
  • institutionId stringPre-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'.
  • brandId stringThe 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.
  • countryCodes string[]ISO 3166-1 alpha-2 country codes to scope the connection, e.g. ["AU"] or ["US"]
  • redirectUri stringOAuth redirect URI for redirect-based flows
  • brokerageSlug stringSnapTrade: 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 retry

Complete 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

  • providerInstanceId stringrequiredSame 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.
  • publicToken stringPlaid public_token from Plaid Link onSuccess — required when payloadKind is "plaid_public_token"
  • institutionId stringInstitution ID from Plaid Link metadata (optional, for display)
  • connectionId stringProvider connection ID from the callback redirect — required when payloadKind is "provider_connection_id"
search_institutionsread-only

Deprecated: 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

  • query stringrequiredInstitution name or keyword to search for
  • countryCodes string[]ISO 3166-1 alpha-2 filter, e.g. ["AU", "GB"]. Omit to search all regions.
  • providerInstanceId stringLimit 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-safe

Remove 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

  • bankId stringrequiredBank ID to remove — get from list_banks
  • permanent booleanRemove 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-only

Search 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

  • query stringrequiredBank name, alias, routing number or web address to search for
  • market stringrequiredISO 3166-1 alpha-2 market, e.g. "US", "AU". Banks are market-scoped.
  • limit integerMaximum banks to return.
get_brandread-only

Get 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

  • brandId stringrequiredBank id, as returned by search_brands (`key`).
list_accountsread-only

Fetch 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

  • bankId stringrequiredBank connection ID — get from list_banks
get_accountread-only

Fetch 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

  • bankId stringrequiredBank connection ID — get from list_banks
  • accountId stringrequiredAccount ID — get from list_accounts or get_bank
get_transactionsread-only

Fetch 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

  • bankId stringrequiredBank connection ID — get from list_banks
  • accountId stringrequiredAccount ID — get from list_accounts or get_bank
  • from stringStart date in YYYY-MM-DD format, e.g. "2024-01-01" (default: 30 days ago).
  • to stringEnd date in YYYY-MM-DD format, e.g. "2024-12-31" (default: today).
  • cursor stringPagination 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.
  • sync booleanPlaid 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.
  • syncCursor stringIncremental-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-only

Fetch 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

  • bankId stringrequiredBank connection ID — get from list_banks
  • accountId stringrequiredAccount ID — get from list_accounts or get_bank
get_holdingsread-only

Get 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

  • bankId stringrequiredBank connection ID — get from list_banks
  • accountId stringrequiredInvestment account ID — must have accountType="investment", get from list_accounts
get_tradesread-only

Get 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

  • bankId stringrequiredBank connection ID — get from list_banks
  • accountId stringrequiredInvestment account ID — must have accountType="investment", get from list_accounts
  • from stringStart date in YYYY-MM-DD format, e.g. "2024-01-01" (default: 90 days ago)
  • to stringEnd date in YYYY-MM-DD format, e.g. "2024-12-31" (default: today)
get_loanread-only

Get 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

  • bankId stringrequiredBank connection ID — get from list_banks
  • accountId stringrequiredLoan/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-only

List 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-only

Get the full configuration of one feed by id.

Parameters

  • feedId stringrequiredFeed ID — get from list_feeds
validate_feedread-only

Dry-run validate a feed configuration (new feed if no feedId, update if feedId). Returns errors + warnings WITHOUT writing.

Parameters

  • feedId stringIf provided, validate as an update to this feed
  • name stringInput for name.
  • source "sync" | "extractor" | "upload"Required when validating a NEW feed
  • dataType "transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"Required when validating a NEW feed
  • sourceConfig unknownSource configuration. type must match the feed source: "sync" → bank accounts; "extractor" → email inbox; "upload" → file upload
  • destinationConfig unknownDestination configuration. type determines the provider: "notion", "airtable", "google_sheets", "excel", "database", "webhook", "table", or "app". Get integrationId from list_integrations.
  • fieldMappings object[]Maps source fields to destination columns/properties.
  • dataTypeOptions unknownData-type-specific processing options (only for sync feeds)
  • extractionConfig objectAI extraction configuration for extractor/upload feeds
  • schedule objectAutomated sync schedule
create_feedwritedo not retry

Create a new data-pipeline feed. Validate first with validate_feed. destinationConfig + fieldMappings can be added before the first sync.

Parameters

  • name stringrequiredHuman-readable feed name, e.g. "ANZ Transactions → Notion"
  • source "sync" | "extractor" | "upload"required"sync"=bank API; "extractor"=email/PDF AI; "upload"=file
  • dataType "transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"requiredType of financial data this feed produces
  • sourceConfig unknownSource configuration. type must match the feed source: "sync" → bank accounts; "extractor" → email inbox; "upload" → file upload
  • destinationConfig unknownDestination configuration. type determines the provider: "notion", "airtable", "google_sheets", "excel", "database", "webhook", "table", or "app". Get integrationId from list_integrations.
  • fieldMappings object[]Maps source fields to destination columns/properties.
  • dataTypeOptions unknownData-type-specific processing options (only for sync feeds)
  • extractionConfig objectAI extraction configuration for extractor/upload feeds
  • schedule objectAutomated sync schedule
  • icon stringOptional emoji icon
update_feedwriteretry-safe

Partially update a feed (name, config, schedule). source + dataType are immutable.

Parameters

  • feedId stringrequiredFeed ID — get from list_feeds
  • name stringNew feed name
  • icon stringNew icon
  • sourceConfig unknownSource configuration. type must match the feed source: "sync" → bank accounts; "extractor" → email inbox; "upload" → file upload
  • destinationConfig unknownDestination configuration. type determines the provider: "notion", "airtable", "google_sheets", "excel", "database", "webhook", "table", or "app". Get integrationId from list_integrations.
  • fieldMappings object[]Maps source fields to destination columns/properties.
  • dataTypeOptions unknownData-type-specific processing options (only for sync feeds)
  • extractionConfig objectAI extraction configuration for extractor/upload feeds
  • schedule objectAutomated sync schedule
delete_feedwriteretry-safe

Permanently delete a feed. This cannot be undone.

Parameters

  • feedId stringrequiredFeed ID to delete — get from list_feeds
get_data_type_schemaread-only

Get 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-only

List 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-only

List 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

  • feedId stringFeed ID — get from list_feeds. Single-feed mode.
  • scope "self" | "family"'self' (default): this workspace only. 'family': include portals. Admin+ only.
  • limit numberSingle-feed mode: max jobs to return (default 20, cap 25).
  • limitPerFeed numberCross-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-only

Get the full details of one job by id (status, progress, errors, counts).

Parameters

  • feedId stringrequiredFeed ID — get from list_feeds
  • jobId stringrequiredJob ID — get from list_jobs
trigger_syncwritedo not retry

Manually start a sync run for a feed. Pulls the latest data from the source and writes it to the destination.

Parameters

  • feedId stringrequiredFeed ID to sync — get from list_feeds
trigger_learnwriteretry-safe

Trigger 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

  • feedId stringrequiredFeed ID to learn — get from list_feeds
cancel_jobwriteretry-safe

Cancel an in-progress job. Has no effect on jobs that have already finished.

Parameters

  • feedId stringrequiredFeed ID — get from list_feeds
  • jobId stringrequiredJob 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-only

List all enrichments in the workspace (id, name, type — any of the 16 enrichment types — enabled, dataType, order, feed associations).

No parameters.

get_enrichmentread-only

Get the full configuration of one enrichment by id, including its type-specific config block (ruleConfig, alertConfig, filterConfig, budgetConfig, …).

Parameters

  • enrichmentId stringrequiredEnrichment ID — get from list_enrichments
preview_enrichmentread-only

Run 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

  • feedId stringrequiredFeed ID — get from list_feeds
  • records object[]requiredSample records to enrich (max 50). Keys should match the feed dataType field names, e.g. { description, amount, date, category }
create_enrichmentwritedo not retry

Create 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

  • name stringrequiredHuman-readable name
  • type "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.
  • enabled booleanWhether the enrichment is active (default: true)
  • feedIds string[]Feed IDs to apply to — get from list_feeds
  • allFeeds booleanApply to all feeds of the matching dataType
  • ruleConfig objectRequired for type=rule
  • alertConfig objectRequired for type=alert
  • memoryConfig objectOptional config for type=memory
  • config objectThe 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-safe

Update an existing enrichment. Only include fields to change. The enrichment type cannot be changed. Use get_enrichment first to see the current config.

Parameters

  • enrichmentId stringrequiredEnrichment ID — get from list_enrichments
  • name stringNew name
  • enabled booleanEnable or disable
  • dataType "transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"Change the record shape (record-scoped types only; validated against the type)
  • feedIds string[]Replace feed associations
  • allFeeds booleanApply to all compatible feeds
  • ruleConfig objectReplace rule configuration (rule type only)
  • alertConfig objectReplace alert configuration (alert type only)
  • memoryConfig objectReplace memory configuration (memory type only)
  • config objectReplace 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 retry

Permanently delete an enrichment. This cannot be undone. Field memory patterns are preserved.

Parameters

  • enrichmentId stringrequiredEnrichment ID — get from list_enrichments

Tables

Manage tables, schemas, rows, views, relations, comments, history, and files.

list_tablesread-only

List 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

  • includeDeleted booleanInclude tables in the trash (default: live tables only)
  • cursor stringCursor from a previous page
  • limit integerPage size (default: server default)
get_tableread-only

Get 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

  • tableId stringrequiredTable ID — get from list_tables
create_tablewritedo not retry

Create 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

  • name stringrequiredHuman-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.
  • icon stringOptional emoji shown beside the name
  • kind "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.
  • resourceType stringResource contract for a typed table (e.g. transactions, balances, holdings)
  • fields object[]Fields for a freeform table. Omit for a typed table — its fields are derived.
  • retention objectRow 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-safe

Update 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

  • tableId stringrequiredTable ID — get from list_tables
  • name stringNew name
  • icon stringNew emoji
  • retention objectRow 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 retry

Move 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

  • tableId stringrequiredTable ID — get from list_tables
restore_tablewriteretry-safe

Restore 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

  • tableId stringrequiredTable ID — use list_tables with includeDeleted to find it
query_table_rowsread-only

Read 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

  • tableId stringrequiredTable ID — get from list_tables
  • filter unknownFilter 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" }.
  • search stringCase-insensitive match over the primary field and text fields
  • viewId stringAnswer within a saved view: its filter is merged in and its sort used when none is given
  • sort object[]Sort order as [{ field, direction }] (default: the promoted date field, then row id)
  • fields string[]Field keys to return; omit for every field
  • deleted booleanRead the trash instead of live rows
  • rows boolean | objectThe row page: { cursor?, limit? } (max 500), or false to skip rows and answer groups / calculations only
  • groups objectGroup headers with counts and per-group calculations over the filtered set
  • calculations objectCalculations 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 retry

Write 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

  • tableId stringrequiredTable ID — get from list_tables
  • rows object[]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.
  • mergeOn string[]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.
  • typecast booleanDefault 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-safe

Change 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

  • tableId stringrequiredTable ID — get from list_tables
  • rowId stringrequiredRow ID — get from query_table_rows
  • values objectrequiredOnly the fields to change, keyed by field key. Others keep their current value.
delete_table_rowswritedo not retry

Move 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

  • tableId stringrequiredTable ID — get from list_tables
  • rowIds string[]requiredRow IDs — get from query_table_rows
restore_table_rowswriteretry-safe

Bring soft-deleted rows back. Deleted rows stay restorable for 30 days — query them with query_table_rows and includeDeleted before restoring.

Parameters

  • tableId stringrequiredTable ID
  • rowIds string[]requiredRow ids to restore
aggregate_table_rowsread-only

Compute 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

  • tableId stringrequiredTable ID — get from list_tables
  • op "sum" | "count" | "avg" | "min" | "max"requiredAggregation to compute
  • fieldKey stringField key to aggregate. Required for everything except count.
  • groupBy string[]Field keys to group by, e.g. ["category"] or ["accountName", "currency"]
  • filters object[]Field filters, ANDed together
  • limit integerMaximum groups returned (max 5000)
  • mixedCurrencies booleanSet 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 retry

Add 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

  • tableId stringrequiredTable ID — get from list_tables
  • label stringrequiredHuman-readable field name
  • config objectPer-kind configuration: { options: [{ id, label, color }] } for select/status, { maximum, icon } for rating, { multiple } for user, { prefix, digits } for auto number, { accept } for attachment.
  • format objectDisplay format — { kind: "number", variant: "currency", currency: "AUD", precision: 2 }, { kind: "date", preset: "medium" }. Presentation only; it never rewrites a stored value.
  • required booleanReject a row that leaves this field blank
  • description stringShown in the column tooltip and the API
  • icon stringIcon catalog id, e.g. emoji:store
  • kind "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.
  • source stringFormula 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.
  • preset objectA structured computed field. Lowered to formula source server-side.
  • position integerInsert here; appended when omitted
  • expectedSchemaVersion integerAssert 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 retry

Change 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

  • tableId stringrequiredTable ID
  • fieldId stringrequiredField ID — get from get_table (fields[].id)
  • label stringHuman-readable field name
  • config objectPer-kind configuration: { options: [{ id, label, color }] } for select/status, { maximum, icon } for rating, { multiple } for user, { prefix, digits } for auto number, { accept } for attachment.
  • format objectDisplay format — { kind: "number", variant: "currency", currency: "AUD", precision: 2 }, { kind: "date", preset: "medium" }. Presentation only; it never rewrites a stored value.
  • required booleanReject a row that leaves this field blank
  • description stringShown in the column tooltip and the API
  • icon stringIcon catalog id, e.g. emoji:store
  • kind "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.
  • source stringNew formula source (kind formula)
  • preset objectA structured computed field. Lowered to formula source server-side.
  • expectedSchemaVersion integerAssert 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 retry

Remove 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

  • tableId stringrequiredTable ID
  • fieldId stringrequiredField ID — get from get_table
  • expectedSchemaVersion integerAssert 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-safe

Move a field to a new position. Position is the column ORDER a person sees; it changes no stored value and no field key.

Parameters

  • tableId stringrequiredTable ID
  • fieldId stringrequiredField ID
  • position integerrequiredZero-based target position
  • expectedSchemaVersion integerAssert 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-safe

Designate 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

  • tableId stringrequiredTable ID
  • fieldId stringrequiredField ID to promote
  • expectedSchemaVersion integerAssert 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-only

Dry-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

  • tableId stringrequiredTable ID
  • fieldId stringrequiredField ID — get from get_table
  • kind "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 become
  • config objectConfiguration the new kind would carry (select options, rating maximum …)
create_table_relationwritedo not retry

Link 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

  • tableId stringrequiredTable the relation is added to
  • targetTableId stringrequiredTable it points at — the same table for a self-relation
  • label stringrequiredField name on this table
  • cardinality "one" | "many"one = a row links to at most one target row; many = several
  • reciprocal objectCreate the reverse field on the target table too. Omit for a one-way relation.
  • previewFieldIds string[]Target field ids shown beside the primary value on a chip
  • description stringInput for description.
  • icon stringInput for icon.
  • expectedSchemaVersion integerAssert 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 retry

Set, 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

  • tableId stringrequiredTable holding the row being edited
  • rowId stringrequiredRow whose relation cell changes
  • fieldKey stringrequiredRelation field KEY on that table
  • op "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 order
  • targetRowIds string[]requiredRow ids in the TARGET table
search_relation_targetsread-only

Find 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

  • tableId stringrequiredTable holding the relation field
  • fieldKey stringrequiredRelation field KEY
  • query stringText to match against the target’s name
  • limit integerInput for limit.
validate_table_formularead-only

Compile 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

  • tableId stringrequiredTable the formula belongs to
  • source stringFormula source to check — fields are `{fld:<fieldId>}`, from get_table
  • preset objectStructured preset to check instead
  • fieldId stringThe field being edited, when checking a change to an existing formula
  • sampleRows integerRows to evaluate it against (default 3, 0 = compile only)
list_table_activityread-only

The 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

  • tableId stringrequiredTable ID
  • rowId stringOnly this row’s history
  • event "insert" | "update" | "delete" | "restore" | "purge" | "sync_batch"Only this kind of event
  • limit integerInput for limit.
  • cursor stringFrom a previous page
list_table_viewsread-only

The 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

  • tableId stringrequiredTable ID
create_table_viewwritedo not retry

Save 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

  • tableId stringrequiredTable ID
  • name stringrequiredView name, unique per table
  • scope "shared" | "personal"shared (default) is visible to the whole workspace; personal is the acting user’s alone
  • duplicateOf stringCopy everything but the name from this view, its layout included
  • icon stringIcon catalog id (e.g. emoji:pin); omit for the layout default
  • query objectWhich rows, in what order, grouped how: { filter, sort: [{ field, direction }], group: [{ field }] }. filter is the same tree query_table_rows takes, or null.
  • fields objectField arrangement by key: { order, hidden, widths, pins: { left, right }, wrap }
  • calculations objectFooter calculation per field key, e.g. { amount: "sum" }
  • decorations object | object[]Row colour rules: [{ id, condition, color, style? }]; the first match wins
  • density "comfortable" | "compact" | "tight"Input for density.
  • layout unknownLayout: { 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 retry

Change 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

  • tableId stringrequiredTable ID
  • viewId stringrequiredView ID — from list_table_views
  • name stringInput for name.
  • expectedVersion integerThe view version you read. Omit to edit the version this call reads — pass it when a human is editing the same view.
  • icon string | nullIcon catalog id; null clears it
  • query objectWhich rows, in what order, grouped how: { filter, sort: [{ field, direction }], group: [{ field }] }. filter is the same tree query_table_rows takes, or null.
  • fields objectField arrangement by key: { order, hidden, widths, pins: { left, right }, wrap }
  • calculations objectFooter calculation per field key, e.g. { amount: "sum" }
  • decorations object | object[]Row colour rules: [{ id, condition, color, style? }]; the first match wins
  • density "comfortable" | "compact" | "tight"Input for density.
  • layout unknownLayout: { 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-safe

Remove 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

  • tableId stringrequiredTable ID
  • viewId stringrequiredView ID — from list_table_views
list_row_commentsread-only

Deprecated: 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

  • tableId stringrequiredTable ID
  • rowId stringrequiredRow ID — get from query_table_rows
  • limit integerInput for limit.
  • cursor stringFrom a previous page
add_row_commentwritedo not retry

Deprecated: 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

  • tableId stringrequiredTable ID
  • rowId stringrequiredRow ID — get from query_table_rows
  • body stringrequiredThe comment, plain text
resolve_row_commentwriteretry-safe

Deprecated: 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

  • tableId stringrequiredTable ID
  • commentId stringrequiredComment ID — from list_row_comments
  • resolved booleantrue (default) resolves; false reopens
list_row_messagesread-only

The 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

  • tableId stringrequiredTable ID
  • rowId stringrequiredRow ID — get from query_table_rows
  • limit integerInput for limit.
  • cursor stringFrom a previous page
post_row_messagewritedo not retry

Write 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

  • tableId stringrequiredTable ID
  • rowId stringrequiredRow ID — get from query_table_rows
  • body stringrequiredThe 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.
  • idempotencyKey stringSend the same key again and the message is not written twice
  • attachmentIds string[]Files staged with POST /v1/tables/{tableId}/attachments (no fieldKey) to send with the message, at most 10. An inline image in the body (![alt](attachment:<id>)) must name one of them.
edit_row_messagewriteretry-safe

Change the wording of a message you wrote, within 30 minutes of posting it. The previous wording is kept in the message’s history.

Parameters

  • tableId stringrequiredTable ID
  • messageId stringrequiredMessage ID — from list_row_messages
  • body stringrequiredThe 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-safe

Remove 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

  • tableId stringrequiredTable ID
  • messageId stringrequiredMessage ID — from list_row_messages
restore_row_messagewriteretry-safe

Bring back a message deleted from a row’s thread, within the trash window. Only its author or a workspace admin may.

Parameters

  • tableId stringrequiredTable ID
  • messageId stringrequiredThe deleted message’s ID
resolve_row_messagewriteretry-safe

Close a message’s conversation (resolved: true, the default) or reopen it (resolved: false) without erasing it. The message records who resolved it.

Parameters

  • tableId stringrequiredTable ID
  • messageId stringrequiredMessage ID — from list_row_messages
  • resolved booleantrue (default) resolves; false reopens
react_to_rowwriteretry-safe

Leave (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

  • tableId stringrequiredTable ID
  • rowId stringrequiredRow ID — get from query_table_rows
  • kind stringrequiredThe reaction kind, e.g. vote
  • on booleantrue (default) reacts; false removes the reaction
list_row_historyread-only

Every 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

  • tableId stringrequiredTable ID
  • rowId stringrequiredRow ID — get from query_table_rows
  • limit integerInput for limit.
  • cursor stringFrom a previous page
list_row_attachmentsread-only

The 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

  • tableId stringrequiredTable ID
  • rowId stringrequiredRow ID
delete_table_attachmentwriteretry-safe

Remove 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

  • tableId stringrequiredTable ID
  • assetId stringrequiredAttachment id — from list_row_attachments

Dashboards

Create, inspect, and update dashboards and their widgets.

list_foldersread-only

List folders in the workspace. Optionally scoped to a parent folder.

Parameters

  • parentFolderId string | nullParent folder id. Omit for top level.
get_folderread-only

Get one folder by id.

Parameters

  • folderId stringrequiredInput for folderId.
create_folderwritedo not retry

Create a folder to organize dashboards, widgets, and queries.

Parameters

  • name stringrequiredInput for name.
  • parentFolderId string | nullInput for parentFolderId.
  • icon stringInput for icon.
  • color stringInput for color.
update_folderwriteretry-safe

Partially update a folder (name, icon, color, order, parent).

Parameters

  • folderId stringrequiredInput for folderId.
  • patch objectrequiredInput for patch.
delete_folderwriteretry-safe

Delete a folder. Non-recursive lifts children to the parent and unfiles dashboards; recursive deletes the whole subtree.

Parameters

  • folderId stringrequiredInput for folderId.
  • recursive booleanInput for recursive.
move_folderwriteretry-safe

Move a folder under a new parent (null = top level). Cycles are rejected.

Parameters

  • folderId stringrequiredInput for folderId.
  • newParentFolderId string | nullrequirednull = top level
list_queriesread-only

List 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.
  • feedId stringInput for feedId.
  • tag stringInput for tag.
search_queriesread-only

Search queries by name/slug.

Parameters

  • q stringrequiredInput for q.
  • limit integerInput for limit.
get_queryread-only

Get one query by id.

Parameters

  • queryId stringrequiredInput for queryId.
create_querywritedo not retry

Create a single-feed query. transforms is the validated discriminated-union pipeline.

Parameters

  • slug stringrequiredLowercase kebab-case slug, 3-64 chars
  • name stringrequiredInput for name.
  • description stringInput for description.
  • feedId stringrequiredUse 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.
  • parameters object[]Input for parameters.
  • transforms unknown[]Input for transforms.
  • folderId string | nullInput for folderId.
  • tags string[]Input for tags.
update_querywriteretry-safe

Partially update a query. Supply version for optimistic concurrency (409 on mismatch). The store enforces DAG acyclicity for derived queries.

Parameters

  • queryId stringrequiredInput for queryId.
  • version integerInput for version.
  • patch objectrequiredInput for patch.
delete_querywriteretry-safe

Delete a query. Fails if any widget still references it.

Parameters

  • queryId stringrequiredInput for queryId.
preview_queryread-only

Run a query against the current snapshot and return up to 5000 rows. Unavailable in environments without a render plane wired.

Parameters

  • queryId stringrequiredInput for queryId.
  • params objectInput for params.
create_multi_feed_querywritedo not retry

Create a query that concatenates or joins two or more feeds.

Parameters

  • slug stringrequiredLowercase kebab-case slug, 3-64 chars
  • name stringrequiredInput for name.
  • description stringInput for description.
  • sources object[]requiredTwo or more feed sources to combine.
  • combine unknownrequiredInput for combine.
  • aliases string[]Input for aliases.
  • transforms unknown[]Input for transforms.
  • folderId string | nullInput for folderId.
  • tags string[]Input for tags.
create_static_querywritedo not retry

Create a query backed by a literal in-line rowset.

Parameters

  • slug stringrequiredLowercase kebab-case slug, 3-64 chars
  • name stringrequiredInput for name.
  • description stringInput for description.
  • rows object[]requiredLiteral rowset.
  • types objectInput for types.
  • transforms unknown[]Input for transforms.
  • folderId string | nullInput for folderId.
  • tags string[]Input for tags.
create_query_derived_querywritedo not retry

Create a query derived from an upstream query (transform chain). Acyclicity + max depth 8 enforced by the store.

Parameters

  • slug stringrequiredLowercase kebab-case slug, 3-64 chars
  • name stringrequiredInput for name.
  • description stringInput for description.
  • upstreamQueryId stringrequiredInput for upstreamQueryId.
  • transforms unknown[]Input for transforms.
  • folderId string | nullInput for folderId.
  • tags string[]Input for tags.
list_widgetsread-only

List widgets in the workspace (id, slug, name, shape). Private widgets are filtered.

Parameters

  • shape "view" | "control"Input for shape.
  • tag stringInput for tag.
search_widgetsread-only

Search widgets by name/tags.

Parameters

  • q stringrequiredInput for q.
  • limit integerInput for limit.
get_widgetread-only

Get one widget by id (full spec).

Parameters

  • widgetId stringrequiredInput for widgetId.
create_widgetwritedo not retry

Create 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

  • slug stringrequiredLowercase kebab-case slug, 3-64 chars
  • name stringrequiredInput for name.
  • description stringInput for description.
  • shape "view" | "control"requiredInput for shape.
  • view objectRequired 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.
  • control objectRequired 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.
  • queryId string | nullInput for queryId.
  • queryBindings objectInput for queryBindings.
  • visibility "private" | "workspace" | "public-link" | "public-indexed"Input for visibility.
  • folderId string | nullInput for folderId.
  • tags string[]Input for tags.
update_widgetwriteretry-safe

Partially update a widget. Supply version for optimistic concurrency (409 on mismatch). The store validates shape.

Parameters

  • widgetId stringrequiredInput for widgetId.
  • version integerInput for version.
  • patch objectrequiredInput for patch.
delete_widgetwriteretry-safe

Delete a widget. Fails if it is pinned to any dashboard.

Parameters

  • widgetId stringrequiredInput for widgetId.
publish_widgetwritedo not retry

Set a widget's visibility / publishing options and mint or rotate its shortcode.

Parameters

  • visibility "private" | "workspace" | "public-link" | "public-indexed"requiredInput for visibility.
  • indexable booleanInput for indexable.
  • embedAllowedOrigins string[]Input for embedAllowedOrigins.
  • regenerate booleanInput for regenerate.
  • widgetId stringrequiredInput for widgetId.
list_dashboardsread-only

List 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

  • includeDrafts booleanInclude staged draft dashboards (hidden by default).
get_dashboardread-only

Get one dashboard by id or slug (full layout + widgets + filters).

Parameters

  • dashboardId stringrequiredInput for dashboardId.
create_dashboardwritedo not retry

Create an empty dashboard. Pin widgets with attach_widget. For a full dashboard (widgets + layout) prefer compose_dashboard — one atomic call, no orphaned steps.

Parameters

  • slug stringrequiredLowercase kebab-case slug, 3-64 chars
  • name stringrequiredInput for name.
  • description stringInput for description.
  • folderId string | nullInput for folderId.
  • icon stringInput for icon.
  • visibility "private" | "workspace" | "public-link" | "public-indexed"Input for visibility.
  • filters unknown[]Initial DashboardFilter[]
attach_widgetwritedo not retry

Pin 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

  • dashboardId stringrequiredInput for dashboardId.
  • widgetId stringrequiredInput for widgetId.
  • size "s" | "m" | "l" | "full"s | m | l | full
  • layout objectExplicit grid cell; otherwise appended below existing rows.
set_dashboard_filterwriteretry-safe

Add or replace a global dashboard filter (upsert by filter.id).

Parameters

  • dashboardId stringrequiredInput for dashboardId.
  • filter objectrequiredInput for filter.
publish_dashboardwritedo not retry

Set a dashboard's visibility / publishing options and mint or rotate its shortcode.

Parameters

  • visibility "private" | "workspace" | "public-link" | "public-indexed"requiredInput for visibility.
  • indexable booleanInput for indexable.
  • embedAllowedOrigins string[]Input for embedAllowedOrigins.
  • regenerate booleanInput for regenerate.
  • dashboardId stringrequiredInput for dashboardId.
update_dashboardwriteretry-safe

Update dashboard metadata, layout, filters, or theme. Layout and filter changes require the current version.

Parameters

  • dashboardId stringrequiredInput for dashboardId.
  • version integerInput for version.
  • patch objectrequiredInput for patch.
delete_dashboardwriteretry-safe

Delete a dashboard. Widgets pinned only here become unpinned but remain in the library.

Parameters

  • dashboardId stringrequiredInput for dashboardId.
compose_dashboardwritedo not retry

Create 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

  • operationId stringInput for operationId.
  • name stringrequiredInput for name.
  • description stringInput for description.
  • icon stringInput for icon.
  • folderId string | nullInput for folderId.
  • visibility "private" | "workspace" | "public-link" | "public-indexed"Default "workspace".
  • widgets object[]Widgets to create + pin. Provide either widgets OR fromFeed.
  • setupWidgets object[]Design widget placements that stay in setup until a data source is chosen.
  • fromFeed objectAlternative to widgets: build the curated starter dashboard for this feed (KPI row + signature charts + records table).
  • fromBank objectAlternative 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.
  • dataSource objectWith `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).
  • layout object[]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).
  • filters unknown[]Initial DashboardFilter[]. A pinned date_range filter is added when absent.
  • draft booleanCreate as a staged DRAFT: hidden from the dashboards list until committed via commit_dashboard_draft (renderable via direct link meanwhile).
commit_dashboard_draftwriteretry-safe

Flip 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

  • dashboardId stringrequiredInput for dashboardId.
  • acknowledgeSetup booleanInput for acknowledgeSetup.
list_widget_kindsread-only

List the available (non-deprecated) widget kinds, optionally filtered by category.

Parameters

  • category "chart" | "table" | "kpi" | "control" | "layout" | "custom"Input for category.
describe_widget_kindread-only

Get 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

  • kind stringrequiredInput for kind.
describe_actionsread-only

Describe the available widget action kinds (or one kind) with a minimal default spec.

Parameters

  • kind stringInput for kind.
validate_actionread-only

Dry-run validate an action spec. Pure validation — no mutation.

Parameters

  • action objectAction spec; discriminated by action.kind
validate_widget_specread-only

Dry-run validate a widget view/control spec against a kind's schema. No mutation.

Parameters

  • kind stringrequiredInput for kind.
  • spec objectWidget view/control spec
create_table_saved_viewwritedo not retry

Append a saved view (sort/filter/column state) to a table widget.

Parameters

  • widgetId stringrequiredInput for widgetId.
  • name stringrequiredInput for name.
  • state objectrequiredTableViewState
  • isDefault booleanInput for isDefault.
  • scope "user" | "workspace"Input for scope.
widget_pipeline_appendwritedo not retry

Append a client-pipeline op to a view widget.

Parameters

  • widgetId stringrequiredInput for widgetId.
  • op objectrequiredClient-pipeline op; discriminated by op.kind
widget_pipeline_removewritedo not retry

Remove the client-pipeline op at the given index.

Parameters

  • widgetId stringrequiredInput for widgetId.
  • index integerrequiredInput for index.
widget_pipeline_movewriteretry-safe

Reorder a client-pipeline op from one index to another (post-removal destination index).

Parameters

  • widgetId stringrequiredInput for widgetId.
  • from integerrequiredInput for from.
  • to integerrequiredInput for to.
describe_fieldsread-only

Describe 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.
  • depthLimit integerMax join hops. Default 3.
suggest_widgetsread-only

Suggest 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.
  • feedId stringrequiredThe feed the candidate queries bind to. Required — see list_feeds.
  • limit integerInput for limit.
  • aggregated booleanInput for aggregated.
list_relationshipsread-only

List the workspace feed relationships. Falls back to built-ins when no custom registry is wired.

No parameters.

create_relationshipwritedo not retry

Register a custom feed relationship (join edge in the semantic layer).

Parameters

  • fromFeedDataType "transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"requiredInput for fromFeedDataType.
  • fromField stringrequiredInput for fromField.
  • toFeedDataType "transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"requiredInput for toFeedDataType.
  • toField stringrequiredInput for toField.
  • cardinality "one-to-one" | "many-to-one" | "one-to-many"requiredInput for cardinality.
  • label stringInput for label.
delete_relationshipwriteretry-safe

Delete a custom feed relationship. Built-ins are protected.

Parameters

  • relationshipId stringrequiredInput for relationshipId.
resolve_field_bindingread-only

Dry-run a field-to-channel binding for a widget. Does NOT mutate — bind_widget_field ships in a future phase.

Parameters

  • widgetId stringrequiredInput for widgetId.
  • channel stringrequired'x','y','value','source','target','category', …
  • fieldRef objectrequiredInput for fieldRef.

Custom Widgets

Create and inspect custom dashboard widgets.

generate_custom_widgetread-only

ESCAPE 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

  • source stringrequiredInput for source.
  • manifest objectrequiredInput for manifest.
  • bundleId stringInput for bundleId.

Templates

Discover and apply reusable feed and dashboard templates.

list_templatesread-only

List 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.
  • limit integerInput for limit.
describe_templateread-only

Read a template: its binding manifest (slots/required fields/filters) and portable spec body.

Parameters

  • idOrKey stringrequiredTemplate id or key.
check_template_preconditionsread-only

Check whether a workspace can instantiate a template, given which feed types it has. Returns the gaps (missing feed/field/relationship).

Parameters

  • idOrKey stringrequiredInput for idOrKey.
  • availableFeedTypes "transactions" | "balances" | "trades" | "holdings" | "orders" | "loans" | "receipts" | "invoices" | "documents"[]requiredFeed data types present in the workspace.
instantiate_templatewritedo not retry

Instantiate a template into concrete dashboard/widget entities for the workspace. Resource slots take a typed local feed or bank binding.

Parameters

  • idOrKey stringrequiredInput for idOrKey.
  • slots objectslotId → local feed id or typed feed/bank binding.
  • paramValues objectInput for paramValues.
publish_templatewriteretry-safe

Publish a workspace template. Runs BOTH gates (portability + safety); blocked publishes return the violation list. Mints/rotates a share shortcode.

Parameters

  • id stringrequiredInput for id.
  • visibility "private" | "workspace" | "public-link" | "public-indexed"requiredInput for visibility.
  • indexable booleanInput for indexable.
  • embedAllowedOrigins string[]Input for embedAllowedOrigins.
  • regenerate booleanInput 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-only

List 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 retry

Issue 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

  • name stringrequiredLabel for this access, e.g. "Actual Budget — laptop"
  • app "actual" | "securo" | "other"Which client this is for; tailors the setup instructions only
  • accountIds string[]Limit the access to these accounts — get ids from list_accounts
  • includePendingDefault booleanInclude pending transactions when the app does not ask explicitly
rotate_simplefin_accesswritedo not retry

Issue 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

  • accessId stringrequiredSimpleFIN access id — get from list_simplefin_access
revoke_simplefin_accesswriteretry-safe

Revoke 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

  • accessId stringrequiredSimpleFIN access id — get from list_simplefin_access