Connection check
verified live · 26h ago
well-mcp
Connect your AI to your Well financial data - invoices, companies, contacts.
Tools
43
GitHub stars
—
Installs / wk
—
Licence
—
Transport
streamable-http
Last checked
26h ago
Tools & capabilities
43 toolsRead from the running server on 26h ago.
well_add_contact_channel
value*parent*channel*parent_id*workspace_ididempotency_key
Add a contact channel to a company or person. Wraps the resource-scoped REST endpoints (POST /v1/{companies,people}/:id/{emails,phones,web-links,locations}). channel + the matchi… Add a contact channel to a company or person. Wraps the resource-scoped REST endpoints (POST /v1/{companies,people}/:id/{emails,phones,web-links,locations}). channel + the matching value field: - email → value.email - phone → value.e164_number (E.164; a leading "+" is added if missing) - web_link → value.url (+ optional value.platform, default "website") - location → value.city, value.country (+ optional address_line1/2, region, postal_code) value.label is optional (defaults to "work"). NOTE: adding a phone is supported on a PERSON but NOT on a company (no endpoint) — that combination returns a clear error. To READ existing channels, use well_query_records on the parent (companies/people) or the channel root.
well_create_company
name*descriptionworkspace_ididempotency_key
Create a new company in the current workspace. Use this tool when the user asks to create, add, or register a new company. REQUIRED: name OPTIONAL: description After creation, e… Create a new company in the current workspace. Use this tool when the user asks to create, add, or register a new company. REQUIRED: name OPTIONAL: description After creation, enrichment (logo, domain, industry, tax ID, description fill-in) runs asynchronously in the background. The new company is available immediately for follow-up actions, but enriched fields may take a few seconds to populate — re-query after a brief delay to see them. Returns { success: true, company_id, name } on success, or { success: false, error } on failure.
well_create_invoice_document
invoice_id*workspace_ididempotency_key
Render an existing invoice as a print-ready PDF and attach it as the invoice's source document. The letterhead carries the issuing company's own mark when Well has one on file, an… Render an existing invoice as a print-ready PDF and attach it as the invoice's source document. The letterhead carries the issuing company's own mark when Well has one on file, and otherwise sets the issuer's name as text. Never promise a logo. Use this tool when the user asks to generate, render, or attach a PDF for an invoice that already exists in the workspace. This does NOT email or send the invoice anywhere — it only creates and attaches the file. REQUIRED: invoice_id (the invoice must already exist) Refused if the invoice is already linked to a REAL ingested document (an upload, a connector import, or a provider-issued PDF) — that source of truth is never overwritten. Returns { success: true, invoice_id, document_id, reference_number, file } on success, or { success: false, error } on failure. `file` carries the rendered PDF's name and size plus the links to fetch it: `download_url` (saves the file), `signed_url` (opens it), and `app_url` (the document in Well). Hand the user `download_url` when they ask for the PDF itself. Both signed links stop working at `expires_at`; `app_url` does not.
well_create_invoice_from_data
issuer*statustotalscurrency*due_datereceiver*
+6
Create an invoice in Well from data you extracted by reading an invoice (your own OCR) — you send the structured fields, not the file. Well persists the invoice + its line items +… Create an invoice in Well from data you extracted by reading an invoice (your own OCR) — you send the structured fields, not the file. Well persists the invoice + its line items + payment means using the same pipeline as uploaded documents. Fill every field you can read from the document: - issuer / receiver: { name (required), company_id?, domain?, tax_id? } - reference_number, issue_date (YYYY-MM-DD), due_date? (YYYY-MM-DD), currency (ISO 4217) - totals?: { items_total?, tax_total?, grand_total } - line_items[]: { name, quantity?, unit_price, currency?, tax_rate? } - payment_means?[]: { type, iban?, bic?, scheme? } - status?: draft | issued | paid | canceled ONE CALL IS THE WHOLE WRITE. This tool takes the invoice's status and both parties' company ids, so a create never needs a well_update_invoice after it: - The user asked to DRAFT an invoice → pass status: "draft" here. - You already found the company (well_query_records, well_get_entity) → pass its company_id on that party. Naming the party without its id re-resolves it, which can attach the invoice to the wrong company or create a duplicate one. Creating and then patching the same invoice writes twice and shows the user two confirmations for one action. Put the intent in this call.
well_create_person
emailphonejob_titlelast_namefirst_name*workspace_id
+1
Create a new person (contact) in the current workspace. Use this tool when the user asks to add, create, or register a new contact, employee, or person. REQUIRED: first_name OPTI… Create a new person (contact) in the current workspace. Use this tool when the user asks to add, create, or register a new contact, employee, or person. REQUIRED: first_name OPTIONAL: last_name, job_title After creation, enrichment runs asynchronously in the background. Returns { success: true, person_id, full_name } on success, or { success: false, error } on failure.
well_create_statement_upload
workspace_ididempotency_key
Mint a one-time, short-lived upload slot for a bank-statement file. Use this when the user has a statement file (PDF, or a large CSV/XML) to import; the file's bytes do not travel… Mint a one-time, short-lived upload slot for a bank-statement file. Use this when the user has a statement file (PDF, or a large CSV/XML) to import; the file's bytes do not travel through the model. It returns a single-use upload URL + token; the client (or the user) POSTs the raw file bytes to that URL, and the resulting document enters the exact same import pipeline as an in-app upload (detection, dedup, promotion). This result renders a card in widget-capable hosts right away — do not wait for a poll to make it appear. Once the client has uploaded the file bytes, call well_get_statement_import_result with the document_id below ONE time to learn the outcome. The card refreshes itself as extraction and promotion complete, so a repeated poll only serves your own narration in chat, never the card's rendering. The token authorizes exactly ONE upload to this workspace and expires in 15 minutes. It is burned on first use — a second upload needs a new slot. It cannot be used for anything other than a statement upload. The response's document_id is PRE-ALLOCATED at mint time — the upload has not happened yet, and this exact id is what the document will carry once it does. A call to well_get_statement_import_result before the upload lands is a normal "not_found_yet", not an error.
well_delete_company
can modify data
company_id*workspace_ididempotency_key
Delete a company from the current workspace (soft delete). Use this tool when the user asks to delete, remove, or archive a company. REQUIRED: company_id This soft-deletes the c… Delete a company from the current workspace (soft delete). Use this tool when the user asks to delete, remove, or archive a company. REQUIRED: company_id This soft-deletes the company and its company_person relationships. Linked people records themselves are NOT deleted. Invoices and documents referencing the company are preserved. Returns { success: true, company_id } on success, or { success: false, error } on failure.
well_delete_invoice
can modify data
invoice_id*workspace_ididempotency_key
Delete an invoice from Well (soft delete). REQUIRED: invoice_id Soft-deletes the invoice. Linked line items and payment_means rows are NOT cascade-deleted — they remain in the da… Delete an invoice from Well (soft delete). REQUIRED: invoice_id Soft-deletes the invoice. Linked line items and payment_means rows are NOT cascade-deleted — they remain in the database, orphaned. The delete is reversible only at the database level.
well_delete_person
can modify data
person_id*workspace_ididempotency_key
Delete a person (contact) from the current workspace (soft delete). Use this tool when the user asks to delete, remove, or archive a contact. REQUIRED: person_id This soft-delet… Delete a person (contact) from the current workspace (soft delete). Use this tool when the user asks to delete, remove, or archive a contact. REQUIRED: person_id This soft-deletes the person and its company_person relationships. Linked companies themselves are NOT deleted. The authenticated user cannot delete their own person record. Returns { success: true, person_id } on success, or { success: false, error } on failure.
well_get_burn
read-only
yearmonthmonths_backworkspace_id
Get the workspace's average monthly burn — the trailing average of actual outflows, the exact same computation and number the Well app's avg-burn KPI tile shows. Use this instead o… Get the workspace's average monthly burn — the trailing average of actual outflows, the exact same computation and number the Well app's avg-burn KPI tile shows. Use this instead of summing transactions yourself. Returns `amount` (a positive magnitude, not a signed figure) and `currency`, plus the window it was measured over: `trailing_months` is the window length, `months_in_window` and `months_with_data` say how much of it actually carried outflow. `per_month` is the outflow of each month in that window, oldest first — the numbers the average is the mean of. A month with no recorded outflow is present with `0`, not omitted, so the series and the average always agree; do not drop those months when you describe the trend. Use it to say whether burn is rising or falling. `amount` is the figure the app shows, so it is the one you lead with and the one you call the burn — never put a figure computed from the series in its place. If a dark month looks like a data gap rather than a real month of no spend, say it may be a gap; `amount` stays the headline either way. `change` is a percentage against `baseline`, not against anything in `per_month`. `baseline.period` names the months it was measured over. That window anchors earlier than `amount`, so it normally covers a month this payload does not carry, and you usually cannot recompute `change` from the series. The two windows normally SHARE months, so this is a comparison between two overlapping averages rather than between two independent periods — a small `change` says less about a real shift than the same figure across separate periods would. When you quote `change`, say what it compares: `baseline.period` against the window in `per_month`. To describe movement WITHIN the displayed window, compute it from `per_month` and say so. `trend` is whether the change is GOOD, not which way the number went. `trend_polarity` is `lower_is_better` for burn, so `trend: "up"` means burn FELL (the app renders it green) and `"down"` means burn ROSE. Never narrate `trend: "up"` as burn rising — read the sign of `change` for direction. State the window whenever you present the number. The average always divides by `months_in_window`, so when `months_with_data` is lower the figure is a real average over a window containing dark months, NOT an average of only the months that had spend — say so rather than presenting it as the typical monthly outflow. `unavailable: true` means `amount` is a placeholder rather than a measurement (e.g. nothing has synced yet) — a burn of zero standing on nothing measured is not a reading, so say that instead of reporting a €0 burn. `partial: true` means individual transactions were excluded from an otherwise real figure (e.g. a missing FX rate); disclose the exclusion count and any `hints`. Pass `year` + `month` to measure a past period instead of the live window, and `months_back` to change the window length (default 3). For months of cash left, call `well_get_runway` — it composes this burn with the cash position. For what the spend is made of, call `well_get_cost_structure`. Neither is a decomposition of the other: cost structure covers a single closed month and will not sum to a trailing average. When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass `workspace_id` on the call.
well_get_cash_flow_bridge
read-only
yearmonthworkspace_id
Get the workspace's cash-flow bridge — how the opening cash position became the closing one — the exact same steps the Well app's cash-flow waterfall chart shows. Use this instead… Get the workspace's cash-flow bridge — how the opening cash position became the closing one — the exact same steps the Well app's cash-flow waterfall chart shows. Use this instead of deriving flows from raw transaction reads. Returns `steps` in render order, each `{ label, value, kind }`. `kind` says how to read `value`: - "start" and "total" carry an ABSOLUTE cash position (the opening and closing anchors). - "increase" and "decrease" carry a gross flow MAGNITUDE, always positive. The direction lives in `kind`, not in the sign — never report a "decrease" as a negative number or add the magnitudes as though they were signed. - "unexplained" carries a SIGNED adjustment and is the one kind whose sign you must keep. It appears only when the four measured bars do not reconcile: it is the gap between them and the measured closing position, NOT cash that moved. Report it as an unexplained difference and say the bridge does not fully reconcile. Never fold it into the inflow or outflow figure, and never present it as spending, income, or a transfer. Walk the steps to reach the total: opening, plus every "increase", minus every "decrease", plus any "unexplained". When an "unexplained" step is present that sum equals the "total" exactly. When none is present the bridge reconciled to within a small tolerance — say it reconciles, not that it is exact to the cent. A bridge can also omit the step because an anchor could not be measured at all; the `hints` say so when that happens, so read them before claiming either. This is a SELF-CONTAINED reconciliation: the "start" and "total" steps ARE the opening and closing balances for the period, so do not also call `well_get_cash_position` to fetch them. That tool answers a different question — the balance right now — and pairing it with this one reports today's figure beside last period's bridge as though they were the same reading. An EMPTY `steps` array means neither anchor could be measured (nothing connected, or the reads timed out). That is missing data, NOT a month with no movement — say the bridge is unavailable rather than reporting flat cash. `period_start` and `period_end` are the inclusive `YYYY-MM-DD` bounds these bars cover — a trailing multi-month window, not a single month. Read the period from those fields and state it whenever you present the numbers. Never derive it from today's date, and never assume one month. If both fields are absent, say the period is unknown rather than naming one. Pass `year` + `month` to bridge a past period instead of the live window. Read the window you actually got from `period_start`/`period_end` either way. This is not a decomposition of `well_get_cost_structure`: this reconciles opening to closing cash across inflow and outflow, while cost structure splits a single month's outflow by category. They will not tie out, and presenting one as the breakdown of the other is wrong. If `hints` are present (excluded accounts, an FX gap, or an unexplained residual between the anchors and the flows), disclose them rather than presenting the bridge as balanced. When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass `workspace_id` on the call.
well_get_cash_forecast
read-only
workspace_id
Get the workspace's cash-position forecast — trailing actual month-end balances plus a worst-case projection forward — the exact same series the Well app's "Cash Position Forecast"… Get the workspace's cash-position forecast — trailing actual month-end balances plus a worst-case projection forward — the exact same series the Well app's "Cash Position Forecast" chart draws. Use this instead of projecting cash forward yourself from a cash figure and a burn rate. Returns `entries`, one per month, oldest first, each `{ month, actuals, projection }` where `month` is `YYYY-MM`: - `actuals` is the settled cash position at that month's end, and is null for future months. - `projection` is the worst-case value at that month's end, and is null for past months. So the series has exactly one turn: actuals up to the present, projection after it. Do not fill the nulls in or treat a null as a zero. `anchor` names what the projection counts forward from: `{ month, amount, basis }`, where the first projection point is `amount` minus one month of trailing burn. Name the anchor whenever you present that point — without it a reader cannot tell a projection falling from a base that is weeks old from cash that is actually dropping, and the two produce the same number. `anchor` is absent when nothing is projected. Read `basis` before describing the anchor, because the two values are different readings and only one is a month-end: - `closed_month_end` — `amount` is the settled balance at the end of `month`. When `month` is not the month just gone, say so: the projection for a month already under way was computed without anything that happened in it. - `current_position` — `amount` is today's consolidated cash, which DOES include the month under way. Here `month` is only where the projection starts on the chart's grid, not the date the amount was read, so do not present it as a closing balance. The projection is explicitly WORST CASE — it assumes NO revenue and declines at the trailing average burn until it reaches zero, where it stays clamped. Say so whenever you present it. It is a floor, not a forecast of what will happen, and a workspace that expects income will not follow it. Takes no period. A worst-case projection has no coherent meaning for a month whose real outcome is already known, so this always returns the live forecast — unlike `well_get_cash_position` and `well_get_cost_structure`, which do accept `year`/`month`. If `hints` are present (a short actuals window, excluded accounts, or a burn-coverage gap), disclose them rather than presenting the series as unconditionally complete. This tool renders its own chart card in the host. Do not re-plot the series with a charting or visualisation tool — that draws a second, unbranded copy of a chart the reader is already looking at, from the same numbers. Read the series to answer in words; leave the drawing to the card. For the single months-of-cash-left figure call `well_get_runway`; for the burn rate driving the decline call `well_get_burn`. When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass `workspace_id` on the call.
well_get_cash_position
read-only
yearmonthworkspace_id
Get the workspace's current cash position: total cash on hand right now, converted to the workspace base currency, plus a per-account breakdown — the exact same computation and num… Get the workspace's current cash position: total cash on hand right now, converted to the workspace base currency, plus a per-account breakdown — the exact same computation and numbers the Well app's canvas KPI card shows. Use this instead of summing account balances yourself. Returns `amount`/`currency` (the converted total), `accounts` (per-account contributions: native amount/currency, converted amount, the FX rate applied), `as_of` (the FX-rate anchor date this snapshot is valid for), and `balance_history` when the workspace has one. `balance_history` is the trailing closed month-ends plus today, oldest first. Month-end is the only historical granularity that exists, so describe movement between months, never within one, and never as a daily series. A `null` amount is a month no connected account covered — not a zero balance. The field is absent when there is no reconstructed history, so make a trend claim only when it is present. `unavailable: true` means `amount` is a placeholder, not a real measurement (e.g. no accounts connected yet) — say so plainly rather than presenting it as a real €0 balance. `partial: true` means one or more accounts were excluded from an otherwise real total (e.g. missing FX rate) — mention the exclusion count and any `hints` rather than presenting the number as unconditionally complete. Everything here is backward-looking — no burn rate or runway is implied. Call `well_get_runway` instead for a forward-looking figure. Pass `year` + `month` to read the balance as of the end of a past month instead of today. Note that `balance_history` (the trailing sparkline series) is returned ONLY for the live reading: a historical month has no live final point to anchor a trailing series on, so asking for a period returns the point-in-time balance without the series. When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass `workspace_id` on the call.
well_get_cost_structure
read-only
yearmonthworkspace_id
Get the workspace's cost structure: outflow for the latest closed month, broken down by category — the exact same computation and numbers the Well app's canvas cost-structure donut… Get the workspace's cost structure: outflow for the latest closed month, broken down by category — the exact same computation and numbers the Well app's canvas cost-structure donut chart shows. Use this instead of summing/grouping transactions yourself. Returns `entries` (an array of `{ category, amount, pct }`, sorted by amount descending) and `currency` (the workspace base currency). `amount` is a magnitude (outflow), not signed. `period_start` and `period_end` are the inclusive `YYYY-MM-DD` bounds these amounts cover — always a single month. Read the period from those fields and state it whenever you present the numbers. Never derive it from today's date. Never present the figures as a quarter or a multi-month span. If both fields are absent, say the period is unknown rather than naming one. `rung` names which grouping actually produced these categories — "ledger_account" (the workspace's own chart of accounts), "category_normalized" (Well's auto-categorization), "transaction_type" (a technical fallback bucket), or "uncategorised" (no rung qualified — either nothing covered the month, or a rung had the coverage but too few labelled rows). State it when you present the breakdown so the user knows whether they're looking at their own ledger's categories or Well's. `label_provenance` says whether a human owns those labels, which `rung` cannot — "curated" (a person set or confirmed every one), "machine" (none were confirmed by a person), "mixed" (some of each), or "unlabelled" (the breakdown is not grouped on a category, so the question does not apply). When it is "machine" or "mixed", say the categories were assigned automatically and may be wrong on any individual line, rather than presenting them as the user's own categorization. If `hints` are present (e.g. a coverage caveat about uncategorized spend), disclose them rather than presenting the breakdown as unconditionally complete. Pass `year` + `month` to break down a specific past month instead of the latest closed one. Read the month you actually got from `period_start`/`period_end` either way — a requested month with no data still comes back with whatever the endpoint could cover. When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass `workspace_id` on the call.
well_get_design_tokens
read-only
workspace_id
Get Well's colours, shape and type vocabulary, so a view you compose for Well data looks like Well rather than a generic page. Call this ONLY when you are about to render somethin… Get Well's colours, shape and type vocabulary, so a view you compose for Well data looks like Well rather than a generic page. Call this ONLY when you are about to render something yourself — an HTML artifact, a report, a chart you are drawing. You do not need it to answer in prose or in a markdown table. Do NOT use it to restyle a card a Well tool already drew. Where a tool ships its own card the host renders it, and a second styled copy of the same figures is a duplicate, not an improvement. Returns `colors` (roles, not raw token names — `page_background`, `card_surface`, `text_primary`, `accent`, `positive`, `negative`, ...), `series` (categorical chart colours in the order to consume them), `shape` (corner radius and gap), `fonts`, and `color_scheme`, which tells you which ground to compose against. When it is absent the stylesheet did not declare one — pick a ground from `page_background` rather than assuming. Values come from the same token package the Well app, the browser extension and the tool cards compile against, so they cannot drift from the product.
well_get_entity
read-only
id*root*depthworkspace_id
Read ONE entity with its sub-resources nested in a single call. Convenience over well_get_schema + well_query_records: resolves the field paths for you and returns the single reco… Read ONE entity with its sub-resources nested in a single call. Convenience over well_get_schema + well_query_records: resolves the field paths for you and returns the single record with its related data expanded. depth (relation-nesting BOUNDARY, 1-3, default 1): 1 = the entity + its direct sub-resources (emails, phones, locations, …) 2 = + the sub-resources' related scalars 3 = the full level-3 graph (LARGER payload — use when you need the whole picture) Stops at depth 3. Aggregates are excluded. Each child collection is capped at 50 rows; for a full list or to page a large child collection, use well_query_records on that child root instead.
well_get_investment_holdings
read-only
workspace_idworkspace_connector_id*
Get the live holdings/positions (what's currently held and its value) for a connected Plaid investment account — brokerage, IRA, 401k, etc. WORKFLOW: 1. well_list_connectors() → p… Get the live holdings/positions (what's currently held and its value) for a connected Plaid investment account — brokerage, IRA, 401k, etc. WORKFLOW: 1. well_list_connectors() → pick the ENABLED Plaid connector (connection_status: "enabled") and read its workspace_connector_id directly off the row. 2. well_get_investment_holdings({ workspace_connector_id }) → the current holdings, fetched fresh from Plaid on every call (never stored/stale data). Only works on Plaid connectors that support the investments product — not the MCP-transport connector-tool-passthrough tools (well_list_connector_tools / well_invoke_connector_tool), and not for investment transactions (buy/sell/dividend/fee), which are queryable as ordinary rows via well_query_records on the transactions root instead.
well_get_own_company
read-only
workspace_id
Get which company the workspace itself is: the confirmed own-company anchor (`anchor`) and any detected companies not yet confirmed as it (`candidates`). Use this whenever a quest… Get which company the workspace itself is: the confirmed own-company anchor (`anchor`) and any detected companies not yet confirmed as it (`candidates`). Use this whenever a question turns on "mine" versus "theirs" — my payables, my receivables, invoices I owe, what we billed — and then filter by the `company_id` this returns. Never decide which records are the workspace's own by comparing a company NAME: the same legal entity appears under several labels (a registered name, a trade name, a bank-issued label), so a name filter silently drops rows. Returns `anchor` (`company_id`, `registered_name`, `trade_name`) or null when the workspace has not resolved one yet, and `candidates` (each with `company_id`, names, `role`, `confidence_score`, `state`). `anchor: null` means the workspace has no confirmed own company. Say so plainly and do not promote a candidate to the anchor yourself — a candidate is a detection, not a decision, and confirming one is a user action. Registry tax ids and registered addresses are deliberately not returned. Call this directly — no other tool call is needed first. Both the anchor and the candidates are read from the same workspace this call is scoped to.
well_get_runway
read-only
yearmonthworkspace_id
Get the workspace's current cash runway — cash on hand, trailing-3-month average burn, and months of cash left — the exact same computation and numbers the Well app's canvas KPI ca… Get the workspace's current cash runway — cash on hand, trailing-3-month average burn, and months of cash left — the exact same computation and numbers the Well app's canvas KPI cards show. Use this instead of computing runway yourself from raw account/transaction reads. Returns `cash` (amount + currency), `avg_burn` (amount + currency + trailing_months), `months`, and a `status` discriminator: - "ok" — a finite months figure. - "capped" — runway exceeds 36 months; report as ">36 months", not the raw number. - "infinite" — cash is positive and the workspace isn't burning (net inflow); there is no meaningful "months" figure. - "insufficient_data" — not enough connected cash/transaction data to compute; tell the user to connect a bank/accounting connector (well_list_connectors) instead of guessing. `change` is a percentage against `baseline`. A baseline runway divides a month-end cash reading by a trailing burn window, so it takes two dates to describe: `baseline.period` is the cash month-end it was anchored at, and `baseline.burn_window` is the span of the burn it divided by, which normally ends an earlier month. Never present the baseline as the runway measured on one day. `trend` is whether the change is GOOD, not which way the number moved; `trend_polarity` is `higher_is_better` for runway, so `"up"` means the runway grew. Read the sign of `change` for direction. `partial: true` means some accounts or transactions were excluded from the computation (e.g. missing FX rate) — mention the exclusion counts and any `hints` if present rather than presenting the number as unconditionally complete. Pass `year` + `month` to read a past reporting period instead of the live figure. There is no burn-window option here on purpose: the runway figure composes the endpoint's own trailing burn, so a custom window would pair `months` from one window with `avg_burn` from another and the cash ÷ burn division could not reproduce the headline. Call `well_get_burn` for a different window. For the month-by-month cash series, call `well_get_cash_forecast`. Its forward half is a WORST CASE: it assumes the trailing burn continues and no revenue arrives at all. Never describe it as expected, forecast or likely cash, and never quote a month from it as what the balance will be — say what it is, the floor if nothing changes. A month in the settled half is a measured balance and may be stated plainly. When the token authorizes one workspace, call this directly — no other tool call is needed first. When it authorizes several, this read will not guess which one you mean: pass `workspace_id` on the call.
well_get_schema
read-only
rootdepthworkspace_id
Discover available data types and fields. USAGE: - well_get_schema() → List ALL available roots, including the accounting graph (ledger_accounts, journals, journal_entries) plus a… Discover available data types and fields. USAGE: - well_get_schema() → List ALL available roots, including the accounting graph (ledger_accounts, journals, journal_entries) plus account_balances, tax_rates, exchange_rates — query these for real financial statements (compte de résultat / balance sheet) instead of reconstructing them from raw invoices - well_get_schema({ root: "invoices" }) → List all available fields for invoices WORKFLOW: 1. Call well_get_schema(root) to see available fields 2. Pick the fields you need for your task (typically 5-15) 3. Call well_query_records with those specific fields Returns fields with path, type, and (when documented) semantic context: - { path: "invoices.grand_total", type: "numeric", context: "Total invoice amount incl. tax in the document currency...", enrichment: "AI extraction" } → use _eq, _gt, _lt, etc. - { path: "invoices.local_currency", type: "enum" } → use ONLY _eq, _neq, _in, _nin, _is_null - { path: "invoices.issuer.name", type: "text" } → use _eq, _like, _ilike, etc. - "context" (when present) explains what the field MEANS in the domain and how it's used — read it to pick the right field and write correct filters. - "enrichment" (when present) is the value's provenance (e.g. "Bank sync", "AI extraction", "System generated", "Derived", "Manual"). Use the type to choose the right whereClause operators in well_query_records. To use in well_query_records, convert path to array: "invoices.issuer.name" → ["invoices", "issuer", "name"]
well_get_statement_import_result
read-only
document_id*workspace_id
Read the outcome of a bank-statement upload started with well_create_statement_upload, by the document_id that tool returned. well_create_statement_upload already renders a card f… Read the outcome of a bank-statement upload started with well_create_statement_upload, by the document_id that tool returned. well_create_statement_upload already renders a card from its own result — this tool does not create or redraw it. Call it once, shortly after the client has uploaded the file bytes, to learn what happened. The card refreshes itself as extraction and promotion complete, so a later call here narrates the current state in chat; it never advances or re-renders the card. - status "not_found_yet": the upload has not landed yet — a NORMAL result right after minting the slot, not an error. Poll again once the file has been uploaded. - status "processing": the file is uploaded and the statement is still being extracted / promoted. - status "imported" | "needs_account" | "duplicate" | "skipped" | "failed": the terminal outcome. On "imported", matched_count / review_count / minted_count / already_present_count report the promotion's own snapshot counts, taken once at import time and covering every promotable line of the file disjointly; null on any of them means the row predates count tracking — treat as unknown, never as 0. `records` lists the minted transactions only — matched or ambiguous lines link an existing transaction and are excluded; `graph` is the frozen record graph for the same snapshot; `records_url` opens the workspace's transactions table. This tool reads only — it changes nothing.
well_invoke_connector_tool
can modify data
argstool*workspace_ididempotency_keyworkspace_connector_id*
Run one tool on a connected provider's own MCP server (e.g. create a record in Attio), on behalf of this workspace's connection. Use this ONLY for an action the user explicitly as… Run one tool on a connected provider's own MCP server (e.g. create a record in Attio), on behalf of this workspace's connection. Use this ONLY for an action the user explicitly asked to take on that provider. It is NOT a way to read financial data: Well already syncs invoices, transactions, accounts and the accounting graph from every connected provider — read those with well_query_records instead of calling a provider's own list/read tools. WORKFLOW: 1. well_list_connectors() → pick the ENABLED provider (connection_status: "enabled") and read its workspace_connector_id directly off the row. 2. well_list_connector_tools({ workspace_connector_id }) → the live tool names + input schemas that connection actually exposes right now. 3. well_invoke_connector_tool({ workspace_connector_id, tool: "<one of the names from step 2>", args: { ... } }). Only works on connectors that expose an MCP server (e.g. Attio, Notion, Linear) and whose connection is enabled. Returns the provider's tool result, or { success: false, error } if the tool failed / is not granted.
well_list_connectors
read-only
qkindlimittitleoffsetsubtitle
+2
List the connectors a workspace can install AND everything it has already connected, each with a one-click install deep link. ONE tool answers both halves of the connect question… List the connectors a workspace can install AND everything it has already connected, each with a one-click install deep link. ONE tool answers both halves of the connect question — "what can I connect to Well?" and "what is connected, still syncing, or broken?" — because every existing connection is overlaid onto its catalog row. Do NOT read workspace_connectors records to work out connection coverage; this tool is that answer. Each entry has: - service_id: the connector's stable catalog id (e.g. "stripe"), used in the install link. - name, category_id, direction: what the connector is. - data_domains: the financial domains it serves — any of "bank", "accounting", "invoicing" — or null for a non-financial connector. One connector can serve several domains (Qonto serves all three). "bank" here means the connector delivers cash movements, which a payroll or billing platform also does; do NOT read it as "this is a bank". To list banks, pass kind: "bank", which the server scopes on its own bank classification. - status: "available" connectors are connectable now; "coming_soon"/"unavailable"/"maintenance" are not. - is_matched / is_selected: whether this workspace's detected tools matched this connector / already picked it. - match_score: 0..1 confidence of that match; null when unmatched. A high score is a tool Well is confident the workspace already uses. - is_connected: this workspace has a connection you should REPAIR or MANAGE, not install fresh. True for "enabled", "processing", "error", "need_reconnect" and "suspended"; false for "to_configure" and "disabled", where a fresh install IS the right next step. - connection_status: the existing connection's state, or null when this workspace has no connection row for the connector at all. One of: - "enabled" — connected and syncing. - "processing" — the grant is in and the FIRST sync is still running; data may be partial. Connected: do NOT ask the user to connect it again. - "error" — authenticated but its last real sync failed. Offer install_url as a reconnect. - "need_reconnect" — the grant is dead and only the user can restore it. Offer install_url as a reconnect, NOT a first install. - "suspended" — the connection is held back administratively; tell the user it is paused. The user cannot fix it by reconnecting. - "to_configure" — a connect attempt that never completed its handshake. Nothing is connected: offer install_url as a first install, and never claim the tool is connected. - "disabled" — the connection was torn down. Offer install_url as a first install. A "degraded" connector never appears: it is resolved server-side against its own sync history into "enabled" or "error", so you never surface a state that clears itself. A connection whose state this build cannot read also reports null, and there is_connected stays true — read the two fields together, and treat "null status, is_connected true" as an existing connection whose health is unknown. - workspace_connector_id: the connection instance's id, or null when there is no connection row. This is the id well_invoke_connector_tool and well_list_connector_tools need — resolve it HERE, never via well_query_records on workspace_connectors. - last_successful_sync_at: ISO timestamp of the last SUCCESSFUL sync, or null when none has landed yet. An "enabled" connector with null here has a valid grant but has never delivered data. - sync_in_progress: a data sync is running right now. Tell the user to wait rather than to act. - is_preselected: Well recommends connecting this one now (a high-confidence match with is_connected false). The interactive picker pre-checks exactly these. A "to_configure" or "disabled" row can still be pre-checked — installing it IS the fix. On kind: "accounting" at most ONE row carries it — the single highest-confidence accounting tool — because connecting the accounting software is a pick-one step; every other scope pre-checks each high-confidence match. - install_url: a one-click link that STARTS or REPAIRS the connection in Well. It works from any state — it signs the user in if needed, creates their workspace if they have none, then runs the connector's own auth flow — and it covers banks too (a bank opens its bank-login flow pre-selected). Null only when the connector is not "available". Hand this to the user to get started in one click. install_all_url is a TOP-LEVEL field, not a per-connector one. It is ONE link that installs every installable connector in this result that is not already connected, in the order they are listed. Its reach is wider than the per-row links: a connector the catalog holds by service id alone carries a null install_url and is still installed by this link, so never read a null install_url as "cannot be installed". When the answer offers several connectors to install, hand the user install_all_url and do NOT list the per-connector install_url links beside it — the one link IS the whole offer, and a table of links beside it puts the reader back through several sign-ins. One link carries at most 10 connectors, and install_all_omitted names the service ids it left out, so offer those rows their own install_url. install_all_url is null when the result offers nothing to install. It is null too wherever the result names no set the user has chosen: the unfiltered catalog and the whole bank domain never carry the link, and a name search or an accounting or invoicing domain carries it only while the WHOLE result fits in one link and this page holds all of it — past that the link would stand for whichever rows the page happened to carry. from_selection always carries the link, however many vendors were picked, because the user named each one. Where install_all_url is null, the rows' own install_url links ARE the offer: list them, and never announce a batch link the result does not carry. Scoping: pass kind ("bank" | "accounting" | "invoicing") to get only the connectors serving that domain — the whole set, server-ordered, including the long tail of bank institutions. Pass q to name-search the full catalog. Omit all three for the curated, matched-first view. Use well_list_connector_tools for a live connection's actions. Pass from_selection: true for the connect step that FOLLOWS a vendor pick: it returns ONLY the connectors behind the counterparties the user picked on the missing-invoices card this session, every installable one pre-checked, because the user already chose them. It takes no q and no kind — those browse a catalog, and this names a set already decided. An empty list means the session holds no pick for this workspace, or no picked counterparty matched a connector. Every result carries scope — "catalog", one of the three domains, or "picked_vendors" — naming what the list IS. The card words its title from that field, so a caller must not describe the result as a domain the scope does not name.
well_list_connector_tools
read-only
workspace_idworkspace_connector_id*
Discover the actions a connected provider exposes (e.g. "what can I do with Attio?"). WORKFLOW: 1. well_list_connectors() → pick the ENABLED provider (connection_status: "enabled"… Discover the actions a connected provider exposes (e.g. "what can I do with Attio?"). WORKFLOW: 1. well_list_connectors() → pick the ENABLED provider (connection_status: "enabled") and read its workspace_connector_id directly off the row. 2. well_list_connector_tools({ workspace_connector_id }) → the actions that provider offers (name + description + input schema). 3. well_invoke_connector_tool({ workspace_connector_id, tool, args }) → run one, shaping args from the input schema returned here. Use this whenever you don't already know a connector's tool names — never guess them. Every response also carries reconnect_url: a deep link to the connector's setup page in the web app. When success is false or status is "need_reconnect" (the provider's token is stale/revoked, so no tools come back), give the user reconnect_url so they can re-authenticate the connector. Surface it as a clickable link; never invent connector URLs.
well_list_counterparties
read-only
cursorperiodsworkspace_iduncategorized_only
List the workspace's counterparty companies and how each one is CATEGORIZED — the company-level industry labels a counterparty carries. Use it for "which suppliers have no category… List the workspace's counterparty companies and how each one is CATEGORIZED — the company-level industry labels a counterparty carries. Use it for "which suppliers have no category?", "what industries are my counterparties in?", and before categorizing a counterparty so you name real ids instead of guessing. Name a scope, and say whether to keep only the ones missing a category: - `periods: [{ calendar_year, calendar_month }, …]` (1-12): the counterparties whose invoices those months are still missing, categorized ones included, each row tagged with its month and carrying `tx_count`, `base_total_amount` in `base_currency`, and `suggested_retrieval`. Every month must have ended. - `periods` PLUS `uncategorized_only: true`: the same months, keeping ONLY the counterparties that carry no category. Use this whenever the question is which of a period's suppliers still need one, and whenever a step asks the user to categorize them: the categorized ones are not the work, and listing them buries it. - `uncategorized_only: true` alone: a WORKSPACE-WIDE sweep for every counterparty that carries no category, no month involved. Returns 50 rows per page plus `total_count`; `tx_count`, `base_total_amount` and `suggested_retrieval` are null because the call names no period. When `next_cursor` is not null the sweep has more counterparties: call again with `cursor` set to it to read them. It is a POSITION, not a row offset, so categorizing the rows of one page never hides the rows of the next. Only this sweep pages: `cursor` is refused beside `periods`. COST: the period form has no batch endpoint, so each named month is a separate read of that month's spend. Ask for the months the user actually named, not a whole year "to be safe". Every row carries `categories` (`[{ category_id, name }]`) and `is_categorized`. `categorized_count` and `uncategorized_count` count the COUNTERPARTIES OF THE SCOPE, once each however many months they appear in, not the rows returned. Under `uncategorized_only` the result lists the uncategorized ones alone while `categorized_count` still counts the ones it withheld, so the two together are the period's coverage and `uncategorized_count` is the work left. Report both: naming the listed rows as the period's whole counterparty set overstates how much is uncategorized. TO SET a counterparty's categories, call `well_update_company({ company_id, category_ids: [...] })` — that field REPLACES the company's whole set. Read the available labels first with `well_query_records({ root: "categories", whereClause: { category_type: { _eq: "company" } } })`: that is the company-category catalog. It has no curated allowlist — the labels are minted during enrichment — so pass ids from it rather than inventing a taxonomy. `suggested_retrieval` is derived from the PROVIDER match, not from the category. Categorizing a counterparty does not change it; do not tell the user otherwise. This tool only reads. It categorizes nothing, mints no task, connects nothing and fetches no invoice. Call this directly — no other tool call is needed first (workspace is resolved from the caller's authorized token, same as every other well_* tool).
well_list_missing_invoices
read-only
periodsfiscal_yearworkspace_idcalendar_yearfiscal_periodcalendar_month
List the supplier invoices a past period is still missing — the settled spend whose invoice has not been collected, one row per counterparty, exactly as the Well app's expense-invo… List the supplier invoices a past period is still missing — the settled spend whose invoice has not been collected, one row per counterparty, exactly as the Well app's expense-invoices card shows them. Use it for "which invoices am I missing for <month>?" and as the input to fetching them. Name the period ONE way: `{ calendar_year, calendar_month }` (the calendar month, e.g. June 2026 → 2026, 6), `{ fiscal_year, fiscal_period }`, or `periods: [{ calendar_year, calendar_month }, …]` for SEVERAL months in one call (1-12) — or name NO period at all to use the months the user selected on the period card this session (well_list_periods → the user clicks → well_switch_workspace records them). With no period named and no months selected, the call refuses and tells you to run the period step first. Every month must have ended — a current or future month is refused, and so is the adjustment period (13). Duplicate months are refused. COST: there is no batch endpoint, so each named month is a separate read of that month's spend. Ask for the months the user actually named, not a whole year "to be safe". Returns `rows`, ONE per counterparty for the whole call, never one per month. Each row carries `name`, `tx_count` and `base_total_amount` in `base_currency` SUMMED over the months it covers, its own `months` array naming those months (each with that month's `tx_count`, `base_total_amount`, `proof_task_id`, `acquisition_status` and `refusal_reason`), and the route fields `mode`, `available_modes`, `suggested_action`, `matched_provider_name` and `matched_connector_service_id`, which the provider match resolves once per counterparty. NEVER list a counterparty once per month and never present its months as separate gaps: it is one supplier to chase, and one collection covers every month behind it. Name the months a row spans from its `months` array. The envelope's own `months` carries each month's totals (rows are NOT repeated there), `periods_covered` names the months read, and `transaction_count`, `group_count` and `dropped_groups` are totals across every month read. `row_count` counts the DISTINCT counterparties, so it is never the sum of the months' own `row_count`. `dropped_groups` counts the GROUPS that produced no row — party-less bank operations, unresolved counterparties, unnamed companies — never transactions, and `bank_internal` and `unknown` hold one group per month whatever they contain, so quote neither as a quantity of operations. `unknown` and `unnamed_company` ARE categorized expense spend still missing a supplier invoice, so an empty `rows` over a non-zero count is not a complete period; `bank_internal` alone is, since no supplier can invoice a party-less operation. The single-month fields `calendar_year`, `calendar_month`, `fiscal_year`, `fiscal_period` and `period_label` appear ONLY when the call named exactly one month. Every row also carries `transactions` — the counterparty's own lines behind the row, each with `date`, `description` (the bank's remittance text), `category`, `amount`, `currency` and `base_amount`. `amount` is signed and stays in the transaction's own currency, so never add those together across a row; `base_amount` is the same line in `base_currency`, and the magnitudes of those DO add up to `base_total_amount`. The list is capped at 25 per row and `transactions_omitted` says how many the cap left out — quote that number instead of implying the list is complete. `mode` is the ONE route the card suggests for that row: `agent` (a browser agent can collect it from the supplier portal), `connect` (connect the named service and Well fetches it), `upload` (the user supplies the file). `available_modes` lists every route the row offers instead of only the suggested one — `agent` and `upload` on every row, plus `connect` when the catalog holds a connector for the matched provider, so 2 or 3 entries. Present `mode` as the suggestion and `available_modes` as the choice. Only CATEGORIZED expense transactions are considered — uncategorized spend is not listed, so poor categorization coverage under-reports the gaps; disclose the `hints`. This tool reads the user's data and changes none of it. It does not mint tasks, start a close, connect anything, or fetch any invoice. Call this directly — no other tool call is needed first (workspace is resolved from the caller's authorized token, same as every other well_* tool).
well_list_periods
read-only
yeartitlemonthspurposesubtitleworkspace_id
List the recent accounting months of the workspace, with each month's close status, its invoice-retrieval state, and the counts that describe how much work it holds. Use this to as… List the recent accounting months of the workspace, with each month's close status, its invoice-retrieval state, and the counts that describe how much work it holds. Use this to ask the user WHICH month or months to work on before any close, review, or month-scoped read — do not guess a month, and do not derive one from today's date yourself. Each entry carries: - calendar_year / calendar_month: the month itself. - fiscal_year / fiscal_period: the same month in the workspace's fiscal calendar — this is the pair every close endpoint and close tool takes. - label: the month written out, e.g. "March 2026". - is_complete: the calendar month has ended. A still-accruing month is never a valid close target. - selectable: the month can be CLOSED. False for a month that has not ended, one already closed, one with nothing to close, and a December whose year-end close is not supported yet. Read this one for a close pick. - inspectable: the month can be LOOKED INTO. A reader can open its transactions, its missing invoices and its days. True for EVERY month that has begun, the month in progress included. False only for a month that has not begun. It reads no close verdict and no activity count, so a closed month, an empty month and a workspace with no accounting connector at all still have readable months. An empty month answers with an empty list, which is an answer. Read this one for a retrieval or review pick; every selectable month is also inspectable. - close_status: "closeable" (ready), "not_ready" (work remains), "closed" (already locked), "nothing_to_close" (no activity), or null when the workspace has no verdict for that month. - close_reason: the blocking reason behind the status, or null. - invoice_state: "missing_invoices" (at least one counterparty still owes a supplier invoice), "has_invoices" (checked, and nothing is missing), or "none" (no state: no activity, the month has not begun, or the check could not run). Never read "none" as "nothing missing". - missing_invoice_count: how many counterparties owe an invoice for the month — the rows `well_list_missing_invoices` would return. 0 whenever invoice_state is "none", including when the check did not run. - transaction_count: how many transactions the month holds, whatever delivered them. - bank_transaction_count: the subset of transaction_count delivered by a connector the workspace actually BANKS with, meaning a bank, a neobank, or a treasury or spend platform whose product is an account. An accounting platform and a payment processor deliver transactions too, so transaction_count is NOT a bank signal. Only this field answers "has a bank fed this month". A transaction counts as not-bank when its source connector is unknown, or when that connector has since been disconnected, so a zero here never licenses skipping a bank-connection step. - unposted_invoice_count: invoices the month HAS that have not posted to the ledger. This is a posting gap, not a missing invoice — do not present it as one. - uncategorized_transactions: transactions in the month not yet categorized — the "help categorize" errand behind a not-ready close. - categorized_unposted_transactions: categorized transactions not yet posted to the ledger — part of the "review and book" errand. - days: the DAYS of the month that carry a retrieval state, ascending, each `{ day, state }` over the same vocabulary as invoice_state. A day is "missing_invoices" when it holds settled expense spend still missing its supplier invoice, and "has_invoices" when it holds activity and no such gap. Days with neither are OMITTED, so an absent day means "none". `days` is empty for every month whose invoice_state is "none" — an unchecked month has no day the tool can call clean. This is calendar detail for a picker to paint; quote the month's own counts, not a day list, when answering in prose. - close_days: present ONLY for a `purpose: "close"` call — the DAYS carrying a non-neutral close-readiness state, ascending, each `{ day, state }` over "posted" / "progress". A day absent from it is "neutral" (nothing to close). Calendar detail for the close picker, like `days` is for retrieval. `default_period` is the oldest month that is ready to close, falling back to the oldest still in progress. Offer it as the default choice. It reads `selectable`, so it is null whenever no month in the window can be CLOSED, and a null one does not mean the window is empty: an inspectable month can still be worked on for invoice retrieval. PURPOSE: pass `purpose: "close"` when the user is closing the books, so the picker paints close readiness and each month carries its `close_days` and the categorize / review counts. Omit it (or `purpose: "collect"`) for invoice retrieval, the default. This is the calling skill's intent — set it from the flow, never from the user's phrasing. WINDOW: by default the `months` most recent calendar months, ending with the current one. Pass `year` instead to get ONE calendar year in full — all twelve of its months, December back to January — which is how you reach a year the recent window does not cover, backwards or forwards. `navigable_years` reports the range `year` is answered for. A year ahead of today comes back in full and every month of it is `selectable: false` and `inspectable: false` with `close_reason` "period_not_ended": books close on a month that has ENDED, and a month that has not begun holds nothing to read. Show such months when the user asks to look ahead, and say why they cannot be picked. Never omit them. COST: the invoice state is read per month from a separate endpoint, so a wide window costs one extra read for every month that holds activity, plus one day-coverage read per calendar year those months touch. Ask for the months the user needs, not 24 by default. A wholly future or wholly empty `year` is cheap — no month in it can hold a settled gap, so none is read. Call this directly — no other tool call is needed first (the workspace is resolved from the caller's authorized token, same as every other well_* tool).
well_list_workspaces
read-only
workspace_id
List the workspaces this connection is authorized to access. Use this FIRST when a single token may cover more than one workspace. Each entry has: - workspace_id: pass this as the… List the workspaces this connection is authorized to access. Use this FIRST when a single token may cover more than one workspace. Each entry has: - workspace_id: pass this as the workspace_id argument on other tools to target one workspace. - workspace_name: human-readable name (null if it can't be resolved). - is_primary: true for the token's default workspace (used when you omit workspace_id on a write). - identity: the company behind the workspace (registered name, trade name, registry number, country, website, currency, fiscal year start), so two similarly-named workspaces can be told apart. Every field is null when the workspace has no accounting settings yet. Tax identifiers are deliberately not included. - has_bank_transactions: whether a connector the workspace BANKS with has delivered any transaction to it, meaning a bank, a neobank, or a treasury or spend platform whose product is an account. An accounting platform and a payment processor deliver transactions too and do NOT count here. Neither does a transaction whose source connector is unknown, whose install has since been disconnected, or whose catalog entry has been retired. Only `true` shows that a bank has fed this workspace: `false` means no such transaction was found and `null` means the signal could not be read, so an absent value is never a zero and neither value licenses skipping a bank-connection step. Read this before any month read when the flow needs to know whether the workspace banks with anything at all. The result also carries `session` — what the user's card clicks have already recorded this session: `pinned_workspace_id` (null when not switched), `workspace_queue` (the workspaces to work through next, empty when none), `selected_periods` (the months picked on the period card, empty when none), and `selected_counterparties` (the counterparties picked on the missing-invoices card, with the workspace their company ids belong to; null when none was picked). Call this any time you need to resync with clicks you may have missed. When the token authorizes a single workspace you can omit workspace_id everywhere; when it authorizes several, read tools fan out across all of them unless you pass a workspace_id, and write tools require one.
well_preview_invoice_fetch
read-only
periodsfiscal_yearworkspace_idcalendar_yearfiscal_periodcalendar_month
Preview which vendors a past period is still missing supplier invoices from, where each one's invoices are, and which route would obtain them. Use it for "what would happen if I fe… Preview which vendors a past period is still missing supplier invoices from, where each one's invoices are, and which route would obtain them. Use it for "what would happen if I fetched <month>'s missing invoices?" before anything runs. Name the period ONE way: `{ calendar_year, calendar_month }` (the calendar month, e.g. June 2026 → 2026, 6), `{ fiscal_year, fiscal_period }`, or `periods: [{ calendar_year, calendar_month }, …]` for SEVERAL months in one call (1-12) — or name NO period at all to use the months the user selected on the period card this session (well_list_periods → the user clicks → well_switch_workspace records them). With no period named and no months selected, the call refuses and tells you to run the period step first. Every month must have ended — a current or future month is refused, and so is the adjustment period (13). Duplicate months are refused. COST: there is no batch endpoint, so each named month is a separate read of that month's spend. Ask for the months the user actually named, not a whole year "to be safe". Returns `vendors` — EVERY vendor of the rows THIS CALL covers, one entry per supplier portal ACROSS the whole window (one portal is one place to go, however many months it spans), or one per counterparty where no portal matched: `name`, `provider_id`, `domain`, `url` and `url_source`, the `counterparties` it covers (each tagged with `calendar_year`, `calendar_month`, `period_label` and `suggested_route`), `tx_count`, `base_total_amount` in `base_currency`. THE ROUTE NEVER FILTERS `vendors`: a vendor Well has no published flow and no connector for is listed exactly like the rest, with its route on its counterparties. WHAT the call covers is a separate question, and two fields answer it: a counterparty pick narrows the rows to the picked companies (see `scoped_to_selected_counterparties` below), and a `hints` line names any group the projection could produce no vendor for. So `vendors` is every vendor of the rows THIS CALL covers, which is the whole period only when neither of those is present. `upload_rows` (the user must supply the file) and `connect_rows` (connecting the named service fetches it) carry the same counterparties again, split by route, with the same month tags. WHERE A VENDOR'S INVOICES ARE: `url_source` says how much `url` knows. "blueprint" is the page Well's own published flow opens, so it IS the billing page. "enrichment" is the vendor's front door — the catalog entry address or the company's domain — so the user still has to find the invoices on it. "none" means no address at all and `url` is null. Never present an "enrichment" address as the invoice page. `url_source` informs and gates nothing: an "enrichment" vendor is offered for the pick, and carried on the link, exactly like a "blueprint" one. ROUTES DESCRIBE HOW, NOT WHETHER: a counterparty Well holds a connector for is in `connect_rows` AND under its vendor, where its entry reads `suggested_route: "connect"` and `connect_routed_counterparties` counts it. Connecting is the route to suggest; the agent run stays available so the user has a way through when the connector does not work for them. A counterparty on `suggested_route: "upload"` is in `upload_rows` too. Never present the same counterparty as two separate gaps — it is one gap seen twice, so count it once. `counts` covers the rows this call actually read, and every field states its own unit: `vendors` and `agents` count PORTALS, `agent_tx` counts TRANSACTIONS, `upload` and `connect` count COUNTERPARTY ROWS — one counterparty per month. They are not summable with each other: never add them into one total, and `vendors` is never the sum of the other four, because every counterparty reaches the vendor list whichever route it takes. A total over the whole window counts the DISTINCT counterparties named in `vendors`, and a counterparty appearing again in `upload_rows` or `connect_rows` is the same gap seen by its route. Across several months a counterparty counts once per month in `upload` and `connect`, while `vendors` and `agents` count each portal once for the window, so neither is the sum of the months' own. WHEN `scoped_to_selected_counterparties` IS PRESENT, `vendors`, `upload_rows`, `connect_rows`, `counts` AND `months` COVER ONLY THE PICKED COUNTERPARTIES, NOT THE WHOLE WINDOW: for the months the pick bounded, every row and every figure here is built from the picked rows alone, and `selection_scope` says how many counterparty rows it left out. Never report those rows as every vendor the period is missing an invoice from, and never report those counts as the period's own — state the truncation and its size, and point at `well_list_missing_invoices` for a fresh card that drops the pick. Without that field the counts cover the whole window. `months` gives each month's own counts; `periods_covered` names the months. A sum is `null` when any member of it had no FX rate, never a partial figure. The single-month fields `calendar_year`, `calendar_month`, `fiscal_year`, `fiscal_period` and `period_label` appear ONLY when the call named exactly one month. THIS TOOL LAUNCHES NOTHING. It creates no task, starts no run, and fetches no invoice — `mode` is always `"preview"` and `nothing_launched` is always `true`. Launching the agents is NOT available on this surface, so present the preview as information and do not promise to run it. `collect_url` is the ONE link to hand the user: the `/collect` page, which asks the Well browser extension to run these portals. It names each portal by its `provider_id`, and that id is the only field that decides which portal runs — a name or an address in the link labels a row and nothing more. Give the link as returned and never build one or edit its parameters. The page starts nothing until the user acts on it, it reports which portals the extension accepted, and it never reports that an invoice arrived. The link also names this workspace, and that name gates WHO may act on the link: the page starts nothing until the reader is signed in to Well as a member of it. It does NOT choose where the invoices land — the extension files into whichever workspace it is signed in to — so never tell the user the link picks the destination. THE LINK CARRIES EVERY VENDOR THAT HAS AN ADDRESS, whatever its `url_source` and whether or not Well holds a published flow for it. Deciding what a vendor's invoices need once the page opens belongs to the app and the extension, not to this read, so `url_source` labels a vendor and never withholds it. Two things still keep a vendor off the link: no address at all, and no `provider_id` the link can address. `collect_url` is null when the window holds no addressed vendor at all; `collect_url_omits` names the vendors a full window pushed past the 25-portal ceiling, and `collect_url_unaddressable` names the ones the link cannot name. A vendor on either list is still missing its invoice, so say the link cannot carry it, and offer the upload or the connect route from `upload_rows` and `connect_rows` instead. Never say it has nothing outstanding. Only CATEGORIZED expense transactions are considered — uncategorized spend is not counted, so poor categorization coverage under-reports what an agent run would cover; disclose the `hints`. Call this directly — no other tool call is needed first (workspace is resolved from the caller's authorized token, same as every other well_* tool).
well_query_records
read-only
root*limitcursorfieldsorderByallFields
+3
Query records from Well's database. ⚠️ WORKFLOW: 1. To SHOW the user a table of a record type, just omit `fields`. You never choose columns for presentation: the table the user… Query records from Well's database. ⚠️ WORKFLOW: 1. To SHOW the user a table of a record type, just omit `fields`. You never choose columns for presentation: the table the user sees is ALWAYS the root's display view in the Well web app's column order, trimmed on the widest roots to what fits a chat-width table. 2. To answer a targeted question, call well_get_schema(root) FIRST to discover available fields, then name in `fields` ONLY the extra values you need (5-15 typically). They are ADDED to the display view in the payload you read — they do not replace, reorder, or trim the columns the user sees. ROOTS (read-only — all 33): companies, people, connectors, invoices, documents, transactions, accounts, payment_means, workspace_connectors, memberships, cards, checks, ledger_accounts, journals, journal_entries, tax_rates, exchange_rates, invoice_transactions, categories, account_balances, tasks, workspaces, invoice_payment_means, chat_conversations, blueprint_runs, workspace_connector_sync_logs, media, emails, phones, web_links, locations, invoice_items, billing_events (The accounting graph — ledger_accounts, journals, journal_entries — and balances/rates are read-only projections owned by the sync/posting pipelines; query them for financial context, you cannot create/update them here. Sub-resources like emails/phones/locations are usually richer when read via their parent company/person.) CATEGORY CATALOGS: "categories" holds two independent taxonomies, separated by `category_type`. Always filter on it — an unfiltered read mixes them: - `whereClause: { category_type: { _eq: "company" } }` is the COMPANY-CATEGORY catalog: the industry labels a counterparty carries, and the ids `well_update_company({ category_ids })` accepts. There is no curated allowlist — the labels are minted during enrichment — so read them here rather than inventing a taxonomy. - `whereClause: { category_type: { _eq: "transaction" } }` is the management/transaction taxonomy. CONNECTED TOOLS: do NOT use this tool to show the user what they have connected — call well_list_connectors instead. It owns that job: connection status, and an install link for anything not connected yet. Query root "workspace_connectors" here only for genuine RECORD-level needs — reading sync timestamps, filtering connections, joining them with other roots. ("connectors" is the installable catalog; "workspace_connector_sync_logs" is per-sync history.) Well already syncs the providers' data into the roots above — invoices, transactions, accounts, the accounting graph. ALWAYS read it from here. well_invoke_connector_tool and a provider's own tools are for an ACTION the user explicitly asked to take on that provider (e.g. "create this record in Attio"), never a way to fetch data Well already holds. EXAMPLE - show the user their invoices (no `fields`, ever): well_query_records({ root: "invoices", limit: 50 }) EXAMPLE - answer "how much is still owed on the unpaid invoices?": well_query_records({ root: "invoices", fields: [["invoices", "balance_due"]], whereClause: { "payment_status": { "_in": ["unpaid", "partial"] } } }) // balance_due arrives in the rows for you to total up; the user still sees the // standard invoices table, with its identity, counterparty and status columns. ⚠️ RULES: - `fields` is ADDITIVE — it widens the data you receive, never the table the user sees - Omitting fields (default view) or naming a few extras both beat allFields - Field paths from schema: "invoices.issuer.name" → ["invoices", "issuer", "name"] - Default 50 records per request, max 500. ONE CALL IS THE ANSWER — do not walk the root: Every response already carries `totalCount` (ALL matches, not just this page) and `records_url` (the full web-app table, with your filter and sort already applied). So a request to see a record type is ONE call: the user gets a table of the first page, the count tells them how many there are, and the link takes them to the rest. "Show me all my invoices" is answered by one call + the link — NOT by fetching 483 rows into this conversation. - A non-null `nextCursor` is NOT a to-do. It means more rows exist, which `totalCount` already told you and the link already covers. - Never paginate to compute a total, count, average or breakdown: aggregate over the filtered set instead. Summing a paginated sample produces a wrong number. - Never paginate to "be thorough". Large roots will exhaust the output limit mid-walk, and the user ends up with nothing legible. - Paginate ONLY for per-row work over every match that no aggregate can express, and tell the user the cost before starting. Then: pass the returned `nextCursor` as `cursor`; `nextCursor: null` is the last page. FILTERING (whereClause): - Uses Hasura-style operators on field names. - Safe operators (work on ALL field types): _eq, _neq, _in, _nin, _is_null - Numeric/date only: _gt, _gte, _lt, _lte - Text only: _like, _ilike - When unsure of a field's type, prefer _eq or _in (they always work). - Combine with _and, _or, _not - For relationship fields, use nested syntax: { "issuer": { "company_id": { "_eq": "<company_id>" } } } - NEVER select the workspace's OWN records by matching a company name. One legal entity appears under several labels — a registered name, a trade name, a bank-issued label — so a name filter silently drops rows and the total reads as complete. On the invoices root, pass `partyScope` instead: it resolves the workspace's own side on the server, so this query needs no id lookup and no extra call. Call well_get_own_company for the id only when a root has no `partyScope` and you must filter on issuer_pk / receiver_pk or the nested company_id yourself. - Match a counterparty by id too whenever you have one. Reach for _ilike on a name only to DISCOVER candidates to show the user, never to compute a figure you will report. Examples: { "status": { "_eq": "unpaid" } } { "grand_total": { "_gt": 1000 } } { "local_currency": { "_eq": "EUR" } } { "_and": [{ "status": { "_eq": "unpaid" } }, { "grand_total": { "_gte": 500 } }] } { "issuer": { "company_id": { "_eq": "<company_id from well_get_own_company>" } } } SORTING (orderBy): - Sort by any field: { field: "grand_total", direction: "desc" } - Default sort is by primary key ascending. Returns { rows, totalCount, nextCursor, success }.
well_remove_contact_channel
can modify data
parent*channel*parent_id*channel_id*workspace_ididempotency_key
Remove a contact channel from a company or person. Wraps the resource-scoped DELETE endpoints (DELETE /v1/{companies,people}/:id/{emails,phones,web-links,locations}/:channelId).… Remove a contact channel from a company or person. Wraps the resource-scoped DELETE endpoints (DELETE /v1/{companies,people}/:id/{emails,phones,web-links,locations}/:channelId). Pass channel_id = the UUID of the specific channel row to remove (NOT the parent). Find it by reading the parent with well_query_records and selecting the channel's id field.
well_resolve_reconciliation_task
action*task_ids*workspace_ididempotency_key
Approve or reject one or more reconciliation review tasks (from well_run_register_diff or the in-app review queue). - approve: confirms the match — the link is flipped to active.… Approve or reject one or more reconciliation review tasks (from well_run_register_diff or the in-app review queue). - approve: confirms the match — the link is flipped to active. - reject: dismisses the match — the candidate does not silently re-surface. Each task_id resolves independently; a failure on one (already resolved, not found) is returned in errors and does not block the rest of the batch.
well_resolve_register_diff_gap
task_id*workspace_ididempotency_keybank_ledger_account_id*category_ledger_account_id*
Post a well_run_register_diff gap (one of missing_in_register_ids' review tasks) into QuickBooks as a Purchase or Deposit. Requires the exact ledger_account_id (a UUID, not a name… Post a well_run_register_diff gap (one of missing_in_register_ids' review tasks) into QuickBooks as a Purchase or Deposit. Requires the exact ledger_account_id (a UUID, not a name) for both: - bank_ledger_account_id: the bank/cash account the money moved through (e.g. Checking). - category_ledger_account_id: the expense or income category the gap books against. Look these up first with well_query_records({ root: "ledger_accounts", filters: [...] }) scoped to the register connector — never guess an id or match an account by substring/fuzzy name. Fails with an error (not a silent no-op) if gap posting is disabled for this workspace, if either account doesn't belong to this gap's register connector, or if either account no longer resolves in QuickBooks.
well_run_register_diff
since_dateworkspace_ididempotency_keybank_workspace_connector_id*register_workspace_connector_id*
Diff a workspace's bank transactions against its accounting-register transactions (e.g. QuickBooks), and persist the result. - Every match — hard evidence (structured reference, I… Diff a workspace's bank transactions against its accounting-register transactions (e.g. QuickBooks), and persist the result. - Every match — hard evidence (structured reference, IBAN, tax ID) or inference-only (memo/payee reading) — is raised as a review task with the candidate already attached (raised_for_review). Nothing links automatically; resolve with well_resolve_reconciliation_task once a human decides. - Bank transactions with no register counterpart come back as missing_in_register_ids, each also minted as a gap review task (gaps_proposed) — resolve one with well_resolve_register_diff_gap once a human names the two ledger accounts. gaps_already_proposed counts gaps re-surfaced from an earlier run that already have an open, unresolved proposal. - Bank transactions NOT confirmed absent from the register come back as contended_in_register_ids — never minted as a gap. Two cases land here: (1) a plausible match lost to a higher-confidence sibling transaction this run, so the register-side movement is already accounted for by the winner; (2) the matcher couldn't produce a trustworthy answer (an invalid model response or a provider failure), so absence was never confirmed. Re-run the diff later; a genuine gap or duplicate should resolve itself once the winner's review task is handled or the matcher succeeds. - Register entries no bank transaction explains come back as unexplained_in_register_ids. Returns { enabled: false, ... } with all counts 0 if the workspace's register-diff feature is off.
well_set_own_company
can modify data
company_id*workspace_ididempotency_key
Set which company the workspace itself IS — the confirmed own-company anchor. REQUIRED: company_id — a company that ALREADY EXISTS in this workspace. Obtain it with well_query_rec… Set which company the workspace itself IS — the confirmed own-company anchor. REQUIRED: company_id — a company that ALREADY EXISTS in this workspace. Obtain it with well_query_records (companies) or well_create_company; this tool never creates one. This is a deliberate, accounting-critical write, not a convenience. Anchoring the own company overwrites the workspace's legal identity on its accounting settings (including clearing fields when the anchor moves), records a manual-confirm audit row, and syncs the billing customer name. It never re-posts existing journal entries. Confirm the exact company with the user before calling; never guess one from a name. Only a workspace owner or admin may set the own company. A caller without that role is refused, not silently ignored. well_start_close hard-gates on this anchor: a workspace with no own company cannot start a close.
well_switch_workspace
ackperiodsworkspace_idworkspace_idscounterpartiesidempotency_key
+1
Write this connection's session context — the one place a conversation's standing choices live. This is the tool the widget cards call when the user CLICKS them: the workspace pin… Write this connection's session context — the one place a conversation's standing choices live. This is the tool the widget cards call when the user CLICKS them: the workspace pin and queue, the selected months, the selected counterparties, and the step acknowledgements are all recorded here, and every later tool call defaults to them. Pass any of: - workspace_ids (ordered list): the workspaces to work in. The FIRST entry becomes the pin and the rest the workspace_queue to work through next. Every id must be one this connection is already authorized for — call well_list_workspaces to see them. This grants no new access; it only chooses among the authorized workspaces. - periods: the months the user VALIDATED on the period card ({ calendar_year, calendar_month } each). Period-scoped reads (well_list_missing_invoices, well_preview_invoice_fetch) default to them when called without a period. Send it only for the user's month selection — never to bound a counterparty pick, which would overwrite that selection. - counterparties: the counterparties (vendors) the user selected, each { company_id, matched_connector_service_id }. Copy both ids off the row you listed them from; pass no display name. The selection belongs to the workspace this call is dispatched to, and a switch to another workspace clears it. One session holds one selection, so a new one replaces it; a selection sent for a workspace this connection has switched away from is REFUSED instead, so a card the flow moved past cannot overwrite the pinned workspace's selection. - counterparty_periods: the months the counterparties card listed, sent alongside counterparties. The pick then narrows those months only, and a month it never covered is read in full. With none named, the months this session already holds bound the pick. This never becomes the session's selected months. - ack: "connectors" or "bank" — records that the user confirmed that flow step, in the workspace this call is dispatched to. A switch to another workspace clears it, so the next workspace's card asks for its own click. An acknowledgement sent for a workspace this connection has switched away from is REFUSED instead, so a card the flow moved past cannot un-confirm the step the pinned workspace's own card recorded. Call it with NO argument at all to pin the workspace this call itself is dispatched to — its universal workspace_id, or the only workspace the token covers. workspace_ids is the PIN write and nothing else: it moves the pin AND replaces the workspace_queue, so a one-entry list ends a run that still had workspaces queued. Send it only to change the workspace. A call carrying periods, counterparties or ack needs no workspace_ids: its universal workspace_id targets that one call, and the pin and the queue stay where they are. Never re-pin the workspace this connection already holds. Every provided input is applied, and `changed` names the session fields this call wrote. After a switch, every later call that omits workspace_id targets the pinned workspace, for reads and writes alike; passing workspace_id on a later call overrides it for that call only. well_list_workspaces reports the current session context, and well_wait_for_selection reads a card click back — instantly when it already landed here, after a short wait otherwise. This changes nothing in the user's data.
well_update_company
can modify data
namedomainlocalecompany_id*trade_namedescription
+10
Update an existing company in the current workspace. Use this tool when the user asks to change, fix, rename, or edit a company's fields. REQUIRED: company_id OPTIONAL (only incl… Update an existing company in the current workspace. Use this tool when the user asks to change, fix, rename, or edit a company's fields. REQUIRED: company_id OPTIONAL (only include fields the user wants changed): name, description, domain, registered_name, trade_name, tax_id_value, tax_id_type, registry_country (ISO 3166-1 alpha-2, e.g. "FR"), business_type, registered_value, registry_name, locale (ISO 639-1 two-letter language code, e.g. "en", "fr" — not "en_US"). CATEGORIES (a counterparty's industry): pass `category_ids` — the COMPLETE set of category ids the company should carry. It REPLACES the current set: ids you leave out are unlinked, and `[]` clears every category. Omit the field to leave the categories untouched. Read the catalog first with well_query_records({ root: "categories", whereClause: { category_type: { _eq: "company" } } }) and pass ids from it — an id that is not a `category_type = "company"` row is refused, and this tool never creates a category. NOT CHANGEABLE via this tool: emails, phones, locations, linked people, media. Those require dedicated tools (not yet available). Returns { success: true, company_id, name } on success — plus category_count, the number of categories the company carries afterwards, when the call passed `category_ids`. Returns { success: false, error } on failure.
well_update_invoice
can modify data
termsstatusdue_datetax_totalinvoice_id*issue_date
+13
Update an existing invoice in Well. Call well_get_schema("invoices") to discover all available fields. REQUIRED: invoice_id OPTIONAL (only pass fields you want changed): - refe… Update an existing invoice in Well. Call well_get_schema("invoices") to discover all available fields. REQUIRED: invoice_id OPTIONAL (only pass fields you want changed): - reference_number, issue_date (ISO date), due_date (ISO date) - status (draft | issued | paid | canceled) - terms, description - grand_total, items_total, tax_total (numbers) - local_currency (ISO 4217 three-letter code, e.g. "EUR", "USD") - document_type_code (UN/CEFACT 1001 code, e.g. "380") - billing_context (e.g. subscription, one_time, project, ...) - issuer_company_id / receiver_company_id (uuid to set, null to clear, omit to leave unchanged) Cannot change line items, payment_means, or document attachment via this tool.
well_update_person
can modify data
job_titlelast_nameperson_id*first_nameworkspace_ididempotency_key
Update an existing person (contact) in the current workspace. Use this tool when the user asks to change, fix, rename, or edit a person's fields. REQUIRED: person_id OPTIONAL (on… Update an existing person (contact) in the current workspace. Use this tool when the user asks to change, fix, rename, or edit a person's fields. REQUIRED: person_id OPTIONAL (only include fields the user wants changed): first_name, last_name, job_title. NOT CHANGEABLE via this tool: emails, phones, locations, linked companies, media. Those require dedicated tools (not yet available). Returns { success: true, person_id, full_name } on success, or { success: false, error } on failure.
well_upload_statement_bytes
sha256filename*workspace_idcontent_base64*idempotency_key
Upload a bank statement file's BINARY CONTENT (PDF or image) as base64, so the file's real bytes reach Well without any out-of-band HTTP call. Use it for PDF and image statements… Upload a bank statement file's BINARY CONTENT (PDF or image) as base64, so the file's real bytes reach Well without any out-of-band HTTP call. Use it for PDF and image statements up to 5 MiB decoded (the base64 text may be roughly a third larger). Base64-encode the file's bytes EXACTLY — never re-encode a screenshot, a transcription, or a summary of the file. Optionally send the file's sha256 (hex); the server decodes, hashes, and rejects a mismatch, proving the bytes arrived intact. The response carries content_sha256 and byte_length of the decoded payload — report them for verification. The parsed rows, totals, and import outcome arrive via well_get_statement_import_result with the returned document_id. Text statements (.csv/.txt/.xml) whose contents are verbatim in this conversation can go through well_upload_statement_content instead. The document enters the same import pipeline as an in-app upload (detection, dedup, promotion).
well_upload_statement_content
filename*content_text*workspace_ididempotency_key
Upload a bank statement's TEXT CONTENT (a .csv, .txt, or .xml file) directly, as an alternative to well_create_statement_upload's out-of-band file POST. Use it when the user's sta… Upload a bank statement's TEXT CONTENT (a .csv, .txt, or .xml file) directly, as an alternative to well_create_statement_upload's out-of-band file POST. Use it when the user's statement is a small text file whose contents are verbatim in this conversation (1 MiB decoded limit). Send the content EXACTLY as you received it — never reformat, summarize, transcribe from memory, or reconstruct rows. A mangled relay imports wrong financial data. This path is BEST-EFFORT fidelity: what Well ingests is what you relayed, not a byte-verified copy of the user's file. The response carries content_sha256 and byte_length of what the server received — report them so a corrupted relay is visible. The parsed rows, totals, and import outcome arrive via well_get_statement_import_result with the returned document_id, not in this response. PDFs and images NEVER go here (the model cannot relay their bytes faithfully) — use well_upload_statement_bytes. XML with DOCTYPE/ENTITY declarations is rejected. The document enters the same import pipeline as an in-app upload (detection, dedup, promotion).
well_upsert_accounting_settings
can modify data
countryworkspace_idbase_currencycoa_confirmedidempotency_keyaccounting_framework
+2
Set the workspace's accounting configuration: fiscal year start month, first fiscal year start date, country, base currency, accounting framework, and chart-of-accounts confirmatio… Set the workspace's accounting configuration: fiscal year start month, first fiscal year start date, country, base currency, accounting framework, and chart-of-accounts confirmation. Provide only the fields you are changing; omitted fields are left untouched. An empty call (no fields) is refused. Only a workspace owner or admin may set the accounting configuration. A caller without that role is refused, not silently ignored. Changing the fiscal year start month moves the whole fiscal calendar, so it is REFUSED when a period is locked or a close is in progress — the tool surfaces that refusal rather than forcing it. When the change is allowed, it soft-deletes the workspace's regenerable DRAFT journal entries so they re-mint on the new coordinates; VALIDATED and LOCKED entries are never touched. These are accounting-critical values. Confirm each one with the user before calling and never guess them — do not infer a country, currency, framework, or start month the user did not state. This tool does NOT set the workspace's legal identity (registered name, trade name, tax id, domain): that is owned by well_set_own_company. Use that tool for identity, this one for accounting configuration.
well_wait_for_selection
read-only
kind*timeout_sworkspace_id