Server definition
- Hash
- sha256:704c1d1ce2420b0fb0184f08d669c2d7f8b4f45a92d1e90bb2cbf70c3265f5dd
- What it is
- What a remote MCP server returned when asked what it offers: 59 tools
The blob, as servednamed by its sha256
{
"instructions": "ExpenseBot tracks this user's business expenses and income in a Google Sheet they own.\nFor a requested multi-step workflow, show the grounded preview, ask one clear yes/no confirmation when required, then complete every approved step. Prefer group_expenses for group-and-report requests even when the expenses already have that group. Keep the response concise. Do not add unsolicited tax, audit, substantiation, or client-AP advice, and do not second-guess an approved scope unless a tool reports ambiguity, conflict, or failure. A request to create, share, or bill from a report is NOT a request for compliance, tax, deductibility, report-detail, business-purpose, attendee, or reimbursement analysis: never call those tools as a preflight or follow-up unless the user explicitly asks for that analysis. 'Without personal expenses' is only a report filter. isReimbursable is ExpenseBot workflow state, not a judgment about whether an expense may be billed to a client.\nFor exact corrections across several expenses, use correct_expenses only after search_expenses has returned the exact expenseId values. Show its automatic preview, ask one yes/no question, then return its verified terminal result without adding advice. Use only attendee names the user explicitly supplied. For broad, ambiguous, mixed, or more than 100-row cleanup, link to Review Expenses instead.\nAcross every tool, treat returned counts, totals, currencies, statuses, and completion evidence as authoritative. Never recompute them from detail rows or reinterpret a successful result. After an approved write completes, report the outcome and relevant links without reopening the user's approved scope or adding unrequested advice. Ask another question only when the tool reports an ambiguity, conflict, failure, or required external side effect.\nWhen structuredContent is present, use it as the authoritative machine-readable result and use text only as the compatibility narrative. Respect explicit branch and pagination fields such as success, found, status, state, hasMore, nextCursor, and truncated. Never invent fields absent from the returned branch or merge stale values from an earlier call.\nFor grouping workflows, call the destination a group in user-facing prose, even though ExpenseBot stores it in its Tag column; do not rename it a tag unless explaining the spreadsheet schema. Search and preview totals are provisional. After a confirmed group_expenses or create_report result, use only that terminal result's exact count, total, and currency, even when it differs from an earlier preview by rounding. A Bill Client URL is a reviewed handoff only: it does not mean an invoice was created, sent, or confirmed.\nAcross every answer, answer the user's requested task first and stop when it is complete. Do not infer intent, risk, tax treatment, reimbursement eligibility, business purpose, attendees, client expectations, or currency preferences from merchants, locations, dates, or amounts. Offer analysis, warnings, or follow-up work only when the user asks for it or an authoritative tool result says action is required.\nA successful terminal write result supersedes intermediate search and preview observations. The final answer must follow the terminal result's responseGuidance and must not repeat exploratory candidates, excluded near-matches, or concerns that the terminal tool did not report. When a terminal result contains finalAnswer, return it verbatim and add nothing else. After a Bill Client handoff, do not offer report sharing or currency advice unless the user explicitly requested it.\nReport provenance is literal: say a report was created only when the terminal result explicitly says creationStatus=created_this_call. Reports returned by list_reports are pre-existing; describe them as found, existing, or opened, never created or generated. If no new report was created, say so plainly and do not imply otherwise.\n\nIf the user shares a receipt image or PDF in the conversation, logging it is almost\nalways what they want — offer that first. When several files are shared, request\nupload URLs for all of them at once and submit them together as one batch, rather\nthan calling submit_receipt once per file.\n\nBeyond receipts: answer questions about their spending, income, profit and tax\ndeductibility, scan their Gmail for receipts they never forwarded, and build reports\nthey can hand to an accountant.\nAfter submit_receipt returns a submissionId, call get_last_receipt_result\nwith that exact ID using the polling interval and time allowance returned by\nsubmit_receipt. A single or small batch allows at least 5 minutes; larger\nbatches return a longer allowance based on file count. A pending result is\nnormal, not a failure. If the allowance ends, say processing is continuing\nand the user can check later; never resubmit the same file because that\ncreates a duplicate expense.\nReport its final counts, then show spreadsheetUrl and reviewExpensesUrl.\nFor other expense writes show spreadsheetUrl and reviewExpensesUrl. For income\nwrites, show spreadsheetUrl and reviewIncomeUrl.\nIf the user asks to open their spreadsheet, review expenses or income, scan\nGmail, reconcile, or visit a report workspace, call get_spreadsheet_url. It\nreturns a described catalog of the product's canonical authenticated deep\nlinks. Give the user only the one or two links that match their request.\nWhen the user asks whether a Gmail scan is running, finished, stuck, or what\nit is doing, call get_scan_status. Report its authoritative status and live\nphase. On completion, show spreadsheetUrl and reviewExpensesUrl; if action\nis needed, show gmailScanUrl.\nFor questions about ExpenseBot features, setup, billing, Gmail, reports, or\ntroubleshooting, call search_knowledge instead of guessing. Set fullAnswer:true\non the first call for complete, detailed, or step-by-step instructions; use\nthe default excerpt mode only for discovery or quick answers.\n\nEvery figure comes from the user's own sheet. Never estimate, infer, or fill in\namounts, dates, or merchants the user did not provide — report what the tools return.",
"tools": [
{
"description": "Add an expense without a receipt, or record money a client provided in advance for expenses. Writes directly to the user's expense spreadsheet after confirmation. For an ordinary expense, call list_categories first and provide a configured Expense Account. For a client advance, set isClientAdvance=true and clientName; ExpenseBot reuses or creates the canonical client group, applies Cash advance received, and stores the amount as a negative expense. Use this only for money supplied by a client before related spending, never for vendor refunds, reimbursements, earned fees, retainers, or generic deposits. Use the Manual expense link from get_spreadsheet_url instead when the user needs a reviewed form for multiple entries, credits/refunds, tax or tip breakdowns, per diem, or incomplete details.",
"inputSchema": {
"properties": {
"category": {
"description": "A configured Expense Account returned by list_categories. Do not invent a category.",
"type": "string"
},
"city": {
"description": "City/location",
"type": "string"
},
"clientName": {
"description": "Client name for a client advance, in ordinary language. ExpenseBot resolves the canonical Client – Name group.",
"type": "string"
},
"currency": {
"description": "Currency code (default: USD)",
"type": "string"
},
"date": {
"description": "Expense date (YYYY-MM-DD)",
"format": "date",
"type": "string"
},
"isClientAdvance": {
"description": "True only when a client supplied money in advance for expenses. Requires clientName and does not require category.",
"type": "boolean"
},
"merchant": {
"description": "Merchant/vendor name for an ordinary expense. Not needed for a client advance; ExpenseBot derives it from clientName.",
"type": "string"
},
"notes": {
"description": "Notes or description",
"type": "string"
},
"subtotal": {
"description": "Subtotal before tax",
"type": "number"
},
"tax": {
"description": "Tax amount",
"type": "number"
},
"tip": {
"description": "Tip amount",
"type": "number"
},
"total": {
"description": "Positive amount paid or received. ExpenseBot stores a client advance as negative.",
"type": "number"
}
},
"required": [
"total",
"date"
],
"type": "object"
},
"name": "add_cash_expense",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
},
"expense": {
"additionalProperties": true,
"properties": {
"category": {
"type": "string"
},
"currency": {
"type": "string"
},
"date": {
"format": "date",
"type": "string"
},
"isClientAdvance": {
"type": "boolean"
},
"merchant": {
"type": "string"
},
"receivedAmount": {
"type": "number"
},
"recordId": {
"type": "string"
},
"rowNumber": {
"minimum": 1,
"type": "integer"
},
"tag": {
"type": "string"
},
"total": {
"type": "number"
}
},
"type": "object"
},
"finalAnswer": {
"type": "string"
},
"message": {
"type": "string"
},
"recordId": {
"type": "string"
},
"responseGuidance": {
"type": "string"
},
"reviewExpensesUrl": {
"type": "string"
},
"rowNumber": {
"minimum": 1,
"type": "integer"
},
"spreadsheetUrl": {
"type": [
"string",
"null"
]
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"message"
],
"type": "object"
}
},
{
"description": "Log exactly one user-confirmed income payment manually (cash, check, Stripe payout, etc.). Never loop this tool over a statement, export, table, or multi-row list. For two or more payments, use add_income_from_csv for CSV/TSV/text or add_income_from_file for an image/PDF; those tools stage a duplicate-checked preview and require confirmation before writing. Writes to the Income tab of the user's expense spreadsheet. Useful for income that isn't auto-detected from Gmail or Plaid. Call list_income_categories first and use one of its fixed tax categories; an omitted category defaults to Service income and an unknown category is rejected.",
"inputSchema": {
"properties": {
"amount": {
"description": "Income amount (>0)",
"type": "number"
},
"category": {
"description": "A category returned by list_income_categories (optional; defaults to Service income)",
"type": "string"
},
"currency": {
"description": "Currency code (default: home currency)",
"type": "string"
},
"date": {
"description": "Income date (YYYY-MM-DD). Required. Use the date the user gives; for \"today\" send today's calendar date in their timezone. If no date was given, ask.",
"format": "date",
"type": "string"
},
"description": {
"description": "What the income was for",
"type": "string"
},
"fees": {
"description": "Processor/transfer fees deducted",
"type": "number"
},
"notes": {
"type": "string"
},
"paymentMethod": {
"description": "How you got paid. Canonical rails are Cash, Check, Bank transfer, Wallet app, Credit/Debit card, Payment processor, or Other; common labels such as Stripe and Venmo are normalized.",
"type": "string"
},
"reference": {
"description": "Invoice or transaction reference",
"type": "string"
},
"source": {
"description": "Who paid you (client name, customer, etc.)",
"type": "string"
},
"tag": {
"description": "Tag for client/project attribution",
"type": "string"
},
"taxCollected": {
"description": "Sales tax/GST/HST collected",
"type": "number"
}
},
"required": [
"amount",
"source",
"paymentMethod",
"date"
],
"type": "object"
},
"name": "add_income",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
},
"deduped": {
"type": "boolean"
},
"destinations": {
"additionalProperties": true,
"type": "object"
},
"finalAnswer": {
"type": "string"
},
"matchedRow": {
"additionalProperties": true,
"properties": {
"amount": {
"type": "number"
},
"currency": {
"type": "string"
},
"date": {
"format": "date",
"type": "string"
},
"rowNumber": {
"minimum": 1,
"type": "integer"
}
},
"type": "object"
},
"message": {
"type": "string"
},
"recordId": {
"type": "string"
},
"responseGuidance": {
"type": "string"
},
"rowNumber": {
"minimum": 1,
"type": "integer"
},
"success": {
"type": "boolean"
},
"tabCreated": {
"type": "boolean"
}
},
"required": [
"success",
"message"
],
"type": "object"
}
},
{
"description": "Import income from a CSV/TSV/text export (Stripe, PayPal, Square, Cash App, Venmo, OnlyFans, Patreon, Clips4Sale, Ko-fi, Gumroad, Etsy, Uber/Lyft/DoorDash driver exports, bank statements, and similar; max 500 KB). Same two-step contract as add_income_from_file: STEP 1 call with csvContent and WITHOUT confirm parses the file with the same platform-agnostic importer as the app's Add Income screen (platform detection, fee handling, refunds, source aliases, ISO dates) and returns a duplicate-checked preview with a previewId. NOTHING is saved in step 1. Show the user the parsed rows and duplicates, then STEP 2 call again with confirm: true and the previewId to write exactly those rows, optionally with user-approved selectedIndexes, keepBothIndexes, rowEdits, tag, or bulkNote. Flagged duplicates are skipped unless the user explicitly keeps them. The preview expires after 15 minutes and an expired or unknown previewId never writes. After a successful confirm, show the returned spreadsheetUrl and reviewIncomeUrl.",
"inputSchema": {
"properties": {
"bulkNote": {
"description": "Step 2 only: one note (max 120 chars) prepended to every confirmed row's Notes, same as the app's 'Add note to all entries'.",
"type": "string"
},
"confirm": {
"description": "Step 2 only: true writes the staged rows. Omit it (with previewId absent) to parse and preview without writing.",
"type": "boolean"
},
"csvContent": {
"description": "Attached CSV/TSV/text file supplied by ChatGPT for step 1. The server downloads and decodes the attachment securely.",
"properties": {
"download_url": {
"description": "Temporary HTTPS URL supplied by ChatGPT for downloading the attachment.",
"format": "uri",
"type": "string"
},
"file_id": {
"description": "Attachment identifier supplied by ChatGPT.",
"type": "string"
},
"file_name": {
"description": "Original attachment filename supplied by ChatGPT.",
"type": "string"
},
"mime_type": {
"description": "Attachment MIME type supplied by ChatGPT.",
"type": "string"
}
},
"required": [
"download_url",
"file_id"
],
"type": "object"
},
"csvText": {
"description": "Legacy fallback for MCP clients that send raw CSV/TSV/text content inline. ChatGPT should use csvContent.",
"type": "string"
},
"keepBothIndexes": {
"description": "Step 2 only: preview indexes of duplicate-flagged rows the user explicitly wants to keep anyway (the app's 'Keep Both').",
"items": {
"type": "integer"
},
"type": "array"
},
"paymentMethod": {
"description": "Fallback payment rail for rows where the parser found none (Cash, Check, Bank transfer, Wallet app, Credit/Debit card, Payment processor, or Other).",
"type": "string"
},
"previewId": {
"description": "Step 2 only: the previewId returned by the step-1 call.",
"type": "string"
},
"rowEdits": {
"description": "Step 2 only: user-approved corrections, at most 50 rows. Fields: date (ISO YYYY-MM-DD), source, amount, currency, category, paymentMethod, description, notes, reference, fees, taxCollected, tag.",
"items": {
"properties": {
"fields": {
"additionalProperties": true,
"description": "Only the user-approved income fields to replace on this staged row: date, source, amount, currency, category, paymentMethod, description, notes, reference, fees, taxCollected, or tag.",
"type": "object"
},
"index": {
"description": "Preview row index",
"type": "integer"
}
},
"required": [
"index",
"fields"
],
"type": "object"
},
"type": "array"
},
"selectedIndexes": {
"description": "Step 2 only: preview row indexes to write. Omit to write all staged rows.",
"items": {
"type": "integer"
},
"type": "array"
},
"tag": {
"description": "Optional client/project tag. In step 1 it pre-fills the staged rows; in step 2 it applies to the confirmed selection.",
"type": "string"
}
},
"required": [],
"type": "object"
},
"name": "add_income_from_csv",
"outputSchema": {
"additionalProperties": true,
"allOf": [
{
"if": {
"properties": {
"preview": {
"const": true
}
},
"required": [
"preview"
]
},
"then": {
"required": [
"previewId",
"expiresAt",
"entryCount",
"entries",
"confirmationContract"
]
}
},
{
"if": {
"properties": {
"confirmed": {
"const": true
}
},
"required": [
"confirmed"
]
},
"then": {
"required": [
"previewId",
"written",
"deduped",
"failed",
"unattempted",
"total"
]
}
}
],
"properties": {
"confirmationContract": {
"type": "string"
},
"confirmed": {
"type": "boolean"
},
"deduped": {
"minimum": 0,
"type": "integer"
},
"duplicateCount": {
"minimum": 0,
"type": "integer"
},
"duplicates": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"entries": {
"items": {
"additionalProperties": true,
"properties": {
"amount": {
"type": "number"
},
"currency": {
"type": "string"
},
"date": {
"format": "date",
"type": "string"
},
"duplicate": {
"type": "boolean"
},
"index": {
"minimum": 0,
"type": "integer"
},
"payer": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"entryCount": {
"minimum": 0,
"type": "integer"
},
"expiresAt": {
"format": "date-time",
"type": "string"
},
"expiresInMinutes": {
"minimum": 1,
"type": "integer"
},
"failed": {
"minimum": 0,
"type": "integer"
},
"finalAnswer": {
"type": "string"
},
"idempotentReplay": {
"type": "boolean"
},
"maxChatEntries": {
"minimum": 1,
"type": "integer"
},
"message": {
"type": "string"
},
"preview": {
"type": "boolean"
},
"previewId": {
"type": "string"
},
"projectionTruncated": {
"type": "boolean"
},
"refundRowCount": {
"minimum": 0,
"type": "integer"
},
"rejectedRows": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"responseGuidance": {
"type": "string"
},
"reviewImportUrl": {
"type": "string"
},
"reviewIncomeUrl": {
"type": "string"
},
"spreadsheetUrl": {
"type": [
"string",
"null"
]
},
"success": {
"type": "boolean"
},
"tooLargeForChat": {
"type": "boolean"
},
"total": {
"minimum": 0,
"type": "integer"
},
"totalsByCurrency": {
"additionalProperties": {
"type": "number"
},
"type": "object"
},
"unattempted": {
"minimum": 0,
"type": "integer"
},
"warnings": {
"items": {
"type": "string"
},
"type": "array"
},
"written": {
"minimum": 0,
"type": "integer"
}
},
"required": [
"success",
"message"
],
"type": "object"
}
},
{
"description": "Import income from an attached screenshot, image (JPEG, PNG, WebP, HEIC/HEIF), or PDF (payment screenshots, wallet apps, payout or bank statements; max 10 MB). This is a two-step tool. STEP 1: call it with the file and WITHOUT confirm — ExpenseBot parses the file with the same importer as the app's Add Income screen, checks every row against the user's Income tab for duplicates, and returns a preview with a previewId, exact row count, totals by currency, per-row details, duplicate flags, and any rejected rows. NOTHING is saved in step 1; treat the attachment as consent to parse, not consent to write. Show the user the parsed rows and duplicates, then STEP 2: call again with confirm: true and the previewId to write exactly those rows. Only include user-approved changes in step 2 (selectedIndexes, keepBothIndexes, rowEdits, tag, bulkNote). Flagged duplicates are skipped unless the user explicitly asks to keep them (keepBothIndexes). The preview expires after 15 minutes; an expired or unknown previewId never writes. After a successful confirm, show the returned spreadsheetUrl and reviewIncomeUrl. For a visually complex review (mixed income/expense rows, many edits), send the user to the Add Income app link returned by get_spreadsheet_url instead.",
"inputSchema": {
"properties": {
"bulkNote": {
"description": "Step 2 only: one note (max 120 chars) prepended to every confirmed row's Notes, same as the app's 'Add note to all entries'.",
"type": "string"
},
"confirm": {
"description": "Step 2 only: true writes the staged rows. Omit it (with previewId absent) to parse and preview without writing.",
"type": "boolean"
},
"keepBothIndexes": {
"description": "Step 2 only: preview indexes of duplicate-flagged rows the user explicitly wants to keep anyway (the app's 'Keep Both').",
"items": {
"type": "integer"
},
"type": "array"
},
"mimeType": {
"description": "MIME type of the file (default: image/jpeg)",
"enum": [
"image/jpeg",
"image/png",
"image/webp",
"image/heic",
"image/heif",
"application/pdf"
],
"type": "string"
},
"paymentMethod": {
"description": "Fallback payment rail for rows where the parser found none (Cash, Check, Bank transfer, Wallet app, Credit/Debit card, Payment processor, or Other).",
"type": "string"
},
"photo": {
"description": "Attached image or PDF supplied by ChatGPT for step 1. The server downloads and parses the attachment securely.",
"properties": {
"download_url": {
"description": "Temporary HTTPS URL supplied by ChatGPT for downloading the attachment.",
"format": "uri",
"type": "string"
},
"file_id": {
"description": "Attachment identifier supplied by ChatGPT.",
"type": "string"
},
"file_name": {
"description": "Original attachment filename supplied by ChatGPT.",
"type": "string"
},
"mime_type": {
"description": "Attachment MIME type supplied by ChatGPT.",
"type": "string"
}
},
"required": [
"download_url",
"file_id"
],
"type": "object"
},
"photoBase64": {
"description": "Legacy fallback for MCP clients that send complete image/PDF bytes as base64. ChatGPT should use photo.",
"type": "string"
},
"previewId": {
"description": "Step 2 only: the previewId returned by the step-1 call.",
"type": "string"
},
"rowEdits": {
"description": "Step 2 only: user-approved corrections, at most 50 rows. Fields: date (ISO YYYY-MM-DD), source, amount, currency, category, paymentMethod, description, notes, reference, fees, taxCollected, tag.",
"items": {
"properties": {
"fields": {
"additionalProperties": true,
"description": "Only the user-approved income fields to replace on this staged row: date, source, amount, currency, category, paymentMethod, description, notes, reference, fees, taxCollected, or tag.",
"type": "object"
},
"index": {
"description": "Preview row index",
"type": "integer"
}
},
"required": [
"index",
"fields"
],
"type": "object"
},
"type": "array"
},
"selectedIndexes": {
"description": "Step 2 only: preview row indexes to write. Omit to write all staged rows.",
"items": {
"type": "integer"
},
"type": "array"
},
"tag": {
"description": "Optional client/project tag. In step 1 it pre-fills the staged rows; in step 2 it applies to the confirmed selection.",
"type": "string"
}
},
"required": [],
"type": "object"
},
"name": "add_income_from_file",
"outputSchema": {
"additionalProperties": true,
"allOf": [
{
"if": {
"properties": {
"preview": {
"const": true
}
},
"required": [
"preview"
]
},
"then": {
"required": [
"previewId",
"expiresAt",
"entryCount",
"entries",
"confirmationContract"
]
}
},
{
"if": {
"properties": {
"confirmed": {
"const": true
}
},
"required": [
"confirmed"
]
},
"then": {
"required": [
"previewId",
"written",
"deduped",
"failed",
"unattempted",
"total"
]
}
}
],
"properties": {
"confirmationContract": {
"type": "string"
},
"confirmed": {
"type": "boolean"
},
"deduped": {
"minimum": 0,
"type": "integer"
},
"duplicateCount": {
"minimum": 0,
"type": "integer"
},
"duplicates": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"entries": {
"items": {
"additionalProperties": true,
"properties": {
"amount": {
"type": "number"
},
"currency": {
"type": "string"
},
"date": {
"format": "date",
"type": "string"
},
"duplicate": {
"type": "boolean"
},
"index": {
"minimum": 0,
"type": "integer"
},
"payer": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"entryCount": {
"minimum": 0,
"type": "integer"
},
"expiresAt": {
"format": "date-time",
"type": "string"
},
"expiresInMinutes": {
"minimum": 1,
"type": "integer"
},
"failed": {
"minimum": 0,
"type": "integer"
},
"finalAnswer": {
"type": "string"
},
"idempotentReplay": {
"type": "boolean"
},
"maxChatEntries": {
"minimum": 1,
"type": "integer"
},
"message": {
"type": "string"
},
"preview": {
"type": "boolean"
},
"previewId": {
"type": "string"
},
"projectionTruncated": {
"type": "boolean"
},
"refundRowCount": {
"minimum": 0,
"type": "integer"
},
"rejectedRows": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"responseGuidance": {
"type": "string"
},
"reviewImportUrl": {
"type": "string"
},
"reviewIncomeUrl": {
"type": "string"
},
"spreadsheetUrl": {
"type": [
"string",
"null"
]
},
"success": {
"type": "boolean"
},
"tooLargeForChat": {
"type": "boolean"
},
"total": {
"minimum": 0,
"type": "integer"
},
"totalsByCurrency": {
"additionalProperties": {
"type": "number"
},
"type": "object"
},
"unattempted": {
"minimum": 0,
"type": "integer"
},
"warnings": {
"items": {
"type": "string"
},
"type": "array"
},
"written": {
"minimum": 0,
"type": "integer"
}
},
"required": [
"success",
"message"
],
"type": "object"
}
},
{
"description": "Log a business mileage trip in ExpenseBot. Useful for realtors, consultants, contractors, and anyone who drives for work. Requires the user to have configured their mileage rate (cents/km or cents/mi) and unit (mi/km) in ExpenseBot Settings. The trip writes a row to their expense spreadsheet with the calculated dollar value. Use the Mileage and travel link from get_spreadsheet_url instead when the user needs Google Maps route calculation, mileage settings, repeated trips, calendar/rideshare import, per diem, or visual review.",
"inputSchema": {
"properties": {
"category": {
"description": "Override the user's default mileage category (optional)",
"type": "string"
},
"city": {
"description": "City (optional)",
"type": "string"
},
"date": {
"description": "Trip date (YYYY-MM-DD). Required. Use the date the user gives; for \"today\" send today's calendar date in their timezone. If no date was given, ask.",
"format": "date",
"type": "string"
},
"destination": {
"description": "Destination address or location (optional)",
"type": "string"
},
"distance": {
"description": "Distance traveled in the user's configured unit (miles or km)",
"type": "number"
},
"notes": {
"description": "Optional user-supplied context stored with the mileage entry.",
"type": "string"
},
"purpose": {
"description": "Business purpose / description of the trip (e.g., 'Client meeting at 1234 Main St')",
"type": "string"
},
"roundTrip": {
"default": false,
"description": "If true, doubles the distance (return trip)",
"type": "boolean"
},
"tag": {
"description": "Tag for client/project attribution (optional)",
"type": "string"
}
},
"required": [
"distance",
"purpose",
"date"
],
"type": "object"
},
"name": "add_mileage_entry",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
},
"finalAnswer": {
"type": "string"
},
"message": {
"type": "string"
},
"responseGuidance": {
"type": "string"
},
"reviewExpensesUrl": {
"type": "string"
},
"rowNumber": {
"minimum": 1,
"type": "integer"
},
"settings": {
"additionalProperties": true,
"properties": {
"allowancePerUnit": {
"type": "number"
},
"unit": {
"type": "string"
}
},
"type": "object"
},
"spreadsheetUrl": {
"type": [
"string",
"null"
]
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"message"
],
"type": "object"
}
},
{
"description": "Use only when the user explicitly asks to check an existing report for compliance issues such as missing business purpose or policy violations. Creating a report, excluding Personal expenses, sharing a report, or billing a client is not a compliance request; never call this tool automatically as a preflight or follow-up for those workflows.",
"inputSchema": {
"properties": {
"reportId": {
"description": "Report ID to check",
"type": "string"
},
"userRequest": {
"description": "The user's exact request explicitly asking for compliance, policy, audit, substantiation, or business-purpose analysis. Do not paraphrase or invent intent.",
"type": "string"
}
},
"required": [
"reportId",
"userRequest"
],
"type": "object"
},
"name": "check_compliance",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
},
"message": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Check whether ExpenseBot supports a specific feature ('does ExpenseBot support X', 'can it integrate with Y'). Searches the public knowledge base and returns a confidence-scored answer + related questions. Works with or without authentication.",
"inputSchema": {
"properties": {
"feature": {
"description": "Feature or integration to check (e.g., 'Xero', 'mileage tracking', 'Plaid')",
"type": "string"
}
},
"required": [
"feature"
],
"type": "object"
},
"name": "check_feature",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
},
"message": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Look up whether a specific expense type or purchase is tax-DEDUCTIBLE (business write-off rules) based on the user's country/jurisdiction. This is a deductibility *advice* tool — use it for questions like 'is Uber deductible', 'can I write off home office', 'are client dinners 50% or 100%'. Do NOT use it for questions about tax REFUNDS received as income (use get_spending_summary with a 'tax refunds' query for those — they're routed to the Income tab, not deduction rules).",
"inputSchema": {
"properties": {
"category": {
"type": "string"
},
"merchant": {
"type": "string"
},
"query": {
"description": "Deductibility question (e.g., 'is Uber tax deductible', 'home office write-off rules')",
"type": "string"
}
},
"required": [
"query"
],
"type": "object"
},
"name": "check_tax_deductibility",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
},
"message": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Safely correct the category, business purpose, or explicit attendees on an exact bounded set of recorded expenses. Examples: 'categorize these as Travel', 'add Client kickoff dinner as the business purpose for these meals', or 'add Fred, Rob, and Lamar to last night's business meal'. First use search_expenses to identify the exact rows, then pass their full expenseId values. A grounded preview is automatic: first call with confirm omitted/false, show the exact count and proposed before-to-after changes, and ask once for approval. Only after explicit approval repeat the same operationId, selection, and change with confirm:true. A premature confirm:true is converted to preview. Attendee names must come explicitly from the user; never infer them. attendeeMode add preserves existing attendees, while replace substitutes only the attendee segment. Business purpose and attendees preserve the structured Notes field, including card, description, inbox, and other typed segments. Formula Notes and changes that exceed the Notes limit are skipped safely. The confirmed result reports applied/conflicted/failed counts and supports Undo. This tool does not omit duplicates, change amounts or dates, infer business context, or run broad Calendar matching. For unsupported or more than 100-row cleanup, send the user to https://www.expensebot.ai/review-expenses?source=mcp.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"change": {
"additionalProperties": false,
"description": "Exactly one correction. The selected type determines which matching value field is required.",
"oneOf": [
{
"properties": {
"type": {
"const": "category"
}
},
"required": [
"category"
]
},
{
"properties": {
"type": {
"const": "businessPurpose"
}
},
"required": [
"businessPurpose"
]
},
{
"properties": {
"type": {
"const": "attendees"
}
},
"required": [
"attendees"
]
}
],
"properties": {
"attendeeMode": {
"description": "Use add unless the user explicitly asks to replace existing attendees.",
"enum": [
"add",
"replace"
],
"type": "string"
},
"attendees": {
"description": "Attendee names explicitly supplied by the user when type is attendees; never infer names.",
"items": {
"type": "string"
},
"maxItems": 25,
"minItems": 1,
"type": "array"
},
"businessPurpose": {
"description": "User-supplied business purpose to apply when type is businessPurpose.",
"type": "string"
},
"category": {
"description": "Configured expense category to apply when type is category.",
"type": "string"
},
"type": {
"description": "The one correction kind to apply to every exact selected expense.",
"enum": [
"category",
"businessPurpose",
"attendees"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
},
"confirm": {
"description": "Omit/false for preview; true only after explicit approval.",
"type": "boolean"
},
"operationId": {
"description": "Stable idempotency key generated once for preview and reused unchanged.",
"pattern": "^expense-bulk:[a-zA-Z0-9_-]{8,100}$",
"type": "string"
},
"selection": {
"additionalProperties": false,
"description": "Exact expenses returned by search_expenses and approved for this bounded correction.",
"properties": {
"items": {
"description": "One exact receipt identity per selected expense; never broaden this list after preview.",
"items": {
"additionalProperties": false,
"properties": {
"expected": {
"additionalProperties": false,
"description": "Optional optimistic-concurrency values copied from the search result.",
"properties": {
"category": {
"description": "Current category observed before preview for conflict detection.",
"type": "string"
},
"notes": {
"description": "Current structured Notes value observed before preview for conflict detection.",
"type": "string"
}
},
"type": "object"
},
"receiptId": {
"description": "Exact full expenseId/Column-Q receipt identity from search_expenses.",
"type": "string"
}
},
"required": [
"receiptId"
],
"type": "object"
},
"maxItems": 100,
"minItems": 1,
"type": "array"
}
},
"required": [
"items"
],
"type": "object"
},
"status": {
"description": "Read operation status using operationId.",
"type": "boolean"
},
"undo": {
"description": "Undo a completed correction within its Undo window.",
"type": "boolean"
}
},
"required": [
"operationId"
],
"type": "object"
},
"name": "correct_expenses",
"outputSchema": {
"additionalProperties": true,
"properties": {
"change": {
"additionalProperties": true,
"type": "object"
},
"confirmation": {
"additionalProperties": true,
"type": "object"
},
"finalAnswer": {
"type": "string"
},
"manifest": {
"additionalProperties": true,
"type": "object"
},
"message": {
"type": "string"
},
"operationId": {
"type": "string"
},
"preview": {
"additionalProperties": true,
"type": "object"
},
"requiresConfirmation": {
"type": "boolean"
},
"responseGuidance": {
"type": "string"
},
"reviewExpensesUrl": {
"type": "string"
},
"status": {
"type": "string"
},
"success": {
"type": "boolean"
},
"undo": {
"additionalProperties": true,
"type": "object"
}
},
"required": [
"success",
"status"
],
"type": "object"
}
},
{
"description": "Create the exact invoice previously returned by prepare_client_invoice. This is a confirmed write: it revalidates the report snapshot and client identity, creates a private editable Google Doc plus private PDF and DOCX copies, records the issued invoice, and supersedes an older active invoice for the same report. It does not send email or create an Income row. If tax is positive, confirmTax must be true.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"confirmTax": {
"description": "True only after the user confirms the displayed tax label and rate.",
"type": "boolean"
},
"operationId": {
"description": "Optional retry key. Reuse it after an uncertain response.",
"type": "string"
},
"preparationId": {
"description": "Short-lived ID returned by prepare_client_invoice.",
"type": "string"
}
},
"required": [
"preparationId"
],
"type": "object"
},
"name": "create_client_invoice",
"outputSchema": {
"additionalProperties": true,
"properties": {
"advanceApplied": {
"type": "number"
},
"artifactWarning": {
"type": "string"
},
"clientName": {
"type": [
"string",
"null"
]
},
"currency": {
"type": "string"
},
"documentUrl": {
"type": [
"string",
"null"
]
},
"docxUrl": {
"type": [
"string",
"null"
]
},
"dueDate": {
"format": "date",
"type": [
"string",
"null"
]
},
"grandTotal": {
"type": "number"
},
"invoiceId": {
"type": [
"string",
"null"
]
},
"invoiceNumber": {
"type": [
"string",
"null"
]
},
"issueDate": {
"format": "date",
"type": [
"string",
"null"
]
},
"pdfUrl": {
"type": [
"string",
"null"
]
},
"recovered": {
"type": "boolean"
},
"sourceReportId": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"success": {
"type": "boolean"
},
"total": {
"type": "number"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Create an expense report from a clear date, client, project, trip, category, or merchant request. Tags are ExpenseBot's grouping layer for clients, projects, project codes, and trips; call list_tags when the user's intended group is unclear. For requests such as 'all expenses in August except personal', set excludePersonal=true; this excludes both the Personal tag and Personal expense category, matching ExpenseBot's Report Wizard. Returns the report summary, exact report link, Bill Client link, applied filters, and a prefilled Report Wizard fallback for criteria that need visual review. Reports scoped to an existing client/project/trip group include matching expenses that are not already assigned to another ordinary report. Can optionally share with recipients. If no unreported matches remain, create no duplicate report and explain that the expenses are already in Reports. Terminal results also include role-appropriate accountingHandoffUrls from the server capability matrix and the existing reviewed file-export workflow. Use only the exact destination the user requested; the link opens the reviewed app flow and does not mean an export occurred. Complete the requested report directly; do not call check_compliance, get_report_details, or tax/deductibility tools before or after it unless the user explicitly asks for that separate analysis.",
"inputSchema": {
"properties": {
"categories": {
"description": "Optional configured expense categories to include in the report.",
"items": {
"type": "string"
},
"type": "array"
},
"dateRange": {
"description": "Time period filter. Use exactly one variant — pick the shape that matches the user's phrasing.",
"oneOf": [
{
"additionalProperties": false,
"properties": {
"month": {
"description": "Month name or 1-12 (e.g., 'March' or '3')",
"type": "string"
},
"type": {
"const": "month",
"type": "string"
},
"year": {
"description": "4-digit year (e.g., 2026)",
"type": "integer"
}
},
"required": [
"type",
"month",
"year"
],
"title": "Specific month",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"quarter": {
"maximum": 4,
"minimum": 1,
"type": "integer"
},
"type": {
"const": "quarter",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"quarter",
"year"
],
"title": "Quarter of a year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "year",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"year"
],
"title": "Full year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"end": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"start": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"type": {
"const": "specific",
"type": "string"
}
},
"required": [
"type",
"start",
"end"
],
"title": "Specific date range",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "relative",
"type": "string"
},
"value": {
"description": "Relative range phrase, e.g., 'last month', 'YTD', 'this quarter', 'last 7 days'",
"type": "string"
}
},
"required": [
"type",
"value"
],
"title": "Relative natural-language range",
"type": "object"
}
]
},
"excludePersonal": {
"description": "Exclude expenses whose Tag is Personal or whose Expense Category is Personal.",
"type": "boolean"
},
"excludedCategories": {
"description": "Expense categories to exclude from the report.",
"items": {
"type": "string"
},
"type": "array"
},
"excludedTags": {
"description": "Existing ExpenseBot groups to exclude from the report.",
"items": {
"type": "string"
},
"type": "array"
},
"recipientRole": {
"default": "reviewer",
"description": "Access for every shareWith recipient. Use reviewer for one-report review, comments, approval, or change requests. Use accountant only when the user explicitly wants ongoing report management and accounting-software access.",
"enum": [
"reviewer",
"accountant"
],
"type": "string"
},
"shareWith": {
"description": "Email addresses to share the report with",
"items": {
"format": "email",
"type": "string"
},
"type": "array"
},
"tags": {
"description": "Existing ExpenseBot groups to include, such as a client, project, project code, property, or trip. Use list_tags first when uncertain.",
"items": {
"type": "string"
},
"type": "array"
},
"title": {
"description": "Custom report title",
"type": "string"
}
},
"type": "object"
},
"name": "create_report",
"outputSchema": {
"additionalProperties": true,
"properties": {
"accountingHandoffUrls": {
"additionalProperties": true,
"type": "object"
},
"accountingSetupUrl": {
"type": "string"
},
"billClientUrl": {
"type": "string"
},
"creationStatus": {
"type": "string"
},
"data": {
"additionalProperties": true,
"properties": {
"appReportUrl": {
"type": "string"
},
"appliedFilters": {
"additionalProperties": true,
"type": "object"
},
"billClientUrl": {
"type": "string"
},
"creationStatus": {
"type": "string"
},
"functionalTotal": {
"type": "number"
},
"homeCurrency": {
"type": "string"
},
"link": {
"type": "string"
},
"personalExcludedCount": {
"minimum": 0,
"type": "integer"
},
"receiptCount": {
"minimum": 0,
"type": "integer"
},
"reportId": {
"type": "string"
},
"title": {
"type": "string"
}
},
"type": "object"
},
"finalAnswer": {
"type": "string"
},
"nextActions": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"reportId": {
"type": "string"
},
"reportUrl": {
"type": "string"
},
"responseGuidance": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Get a short-lived direct PDF download for one exact authorized expense report, plus the exact highlighted in-app report link. Use the in-app report for CSV, XLSX, receipt ZIP (when available), comments, invoicing, and other visually reviewed actions. Use list_reports first to find the reportId.",
"inputSchema": {
"properties": {
"reportId": {
"description": "Report ID from list_reports",
"type": "string"
}
},
"required": [
"reportId"
],
"type": "object"
},
"name": "export_report",
"outputSchema": {
"additionalProperties": true,
"properties": {
"availableInReport": {
"items": {
"type": "string"
},
"type": "array"
},
"expiresAt": {
"format": "date-time",
"type": "string"
},
"notes": {
"type": "string"
},
"report": {
"additionalProperties": true,
"properties": {
"id": {
"type": "string"
},
"spreadsheetId": {
"type": "string"
},
"status": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": [
"title",
"spreadsheetId"
],
"type": "object"
},
"success": {
"type": "boolean"
},
"urls": {
"additionalProperties": true,
"properties": {
"pdf": {
"type": "string"
},
"report": {
"type": "string"
},
"view": {
"type": "string"
}
},
"required": [
"view",
"pdf",
"report"
],
"type": "object"
}
},
"required": [
"success",
"report",
"urls",
"expiresAt",
"availableInReport",
"notes"
],
"type": "object"
}
},
{
"description": "Fetch full details for a specific item returned by `search`. The id encodes the item type:\n • 'expense:<expenseId>' — the durable Column-Q Receipt ID returned by `search`, URL-encoded, e.g. 'expense:RR-123%3A%3Adrive%2Ffile%209'. Never a sheet row number: rows move when the sheet is prepended.\n • 'report:<reportId>' — the alphanumeric Firestore document id from `search` or `list_reports`, e.g., 'report:FQqDglExofsyyQv7aYy4'\n • 'kb:<entryId>' — the knowledge-base entry id from `search`\nAlways use the id exactly as returned by `search` or `list_reports` — do not invent or modify the trailing portion. Returns the full text content + metadata for the AI to cite.",
"inputSchema": {
"properties": {
"id": {
"description": "Item ID from a previous `search` or `list_reports` call. Format: 'expense:<URL-encoded Column-Q expenseId>', 'report:<reportId>' (alphanumeric Firestore doc id, ~20 chars), or 'kb:<entryId>' (knowledge-base entry id).",
"pattern": "^(expense:.+|report:[A-Za-z0-9_-]+|kb:.+)$",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"name": "fetch",
"outputSchema": {
"properties": {
"id": {
"description": "Echoed back from the input",
"type": "string"
},
"metadata": {
"additionalProperties": true,
"type": "object"
},
"text": {
"description": "Full text content for the AI to cite",
"type": "string"
},
"title": {
"type": "string"
},
"url": {
"format": "uri",
"type": "string"
}
},
"required": [
"id",
"title",
"text"
],
"type": "object"
}
},
{
"description": "Safely correct compliance issues on an exact bounded set of recorded expenses. Use only after the user explicitly asks to fix issues found by check_compliance. Use search_expenses to obtain each full expenseId, then pass those exact identities; never select every report row or infer a business purpose, category, or attendee. This compatibility tool uses the same reviewed workflow as correct_expenses: the first call always prepares a grounded preview and makes no change, even if confirm:true arrives early. Show the exact proposed changes and ask once for approval. Only after explicit approval repeat the same operationId, selection, and change with confirm:true. The confirmed result reports applied/conflicted/failed counts and supports status and Undo. For tags/groups use group_expenses; for unsupported or more than 100-row cleanup open https://www.expensebot.ai/review-expenses?source=mcp.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"change": {
"additionalProperties": false,
"description": "Exactly one correction. The selected type determines which matching value field is required.",
"oneOf": [
{
"properties": {
"type": {
"const": "category"
}
},
"required": [
"category"
]
},
{
"properties": {
"type": {
"const": "businessPurpose"
}
},
"required": [
"businessPurpose"
]
},
{
"properties": {
"type": {
"const": "attendees"
}
},
"required": [
"attendees"
]
}
],
"properties": {
"attendeeMode": {
"description": "Use add unless the user explicitly asks to replace existing attendees.",
"enum": [
"add",
"replace"
],
"type": "string"
},
"attendees": {
"description": "Attendee names explicitly supplied by the user when type is attendees; never infer names.",
"items": {
"type": "string"
},
"maxItems": 25,
"minItems": 1,
"type": "array"
},
"businessPurpose": {
"description": "User-supplied business purpose to apply when type is businessPurpose.",
"type": "string"
},
"category": {
"description": "Configured expense category to apply when type is category.",
"type": "string"
},
"type": {
"description": "The one correction kind to apply to every exact selected expense.",
"enum": [
"category",
"businessPurpose",
"attendees"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
},
"confirm": {
"description": "Omit/false for preview; true only after explicit approval.",
"type": "boolean"
},
"operationId": {
"description": "Stable idempotency key generated once for preview and reused unchanged.",
"pattern": "^expense-bulk:[a-zA-Z0-9_-]{8,100}$",
"type": "string"
},
"selection": {
"additionalProperties": false,
"description": "Exact expenses returned by search_expenses and approved for this bounded correction.",
"properties": {
"items": {
"description": "One exact receipt identity per selected expense; never broaden this list after preview.",
"items": {
"additionalProperties": false,
"properties": {
"expected": {
"additionalProperties": false,
"description": "Optional optimistic-concurrency values copied from the search result.",
"properties": {
"category": {
"description": "Current category observed before preview for conflict detection.",
"type": "string"
},
"notes": {
"description": "Current structured Notes value observed before preview for conflict detection.",
"type": "string"
}
},
"type": "object"
},
"receiptId": {
"description": "Exact full expenseId/Column-Q receipt identity from search_expenses.",
"type": "string"
}
},
"required": [
"receiptId"
],
"type": "object"
},
"maxItems": 100,
"minItems": 1,
"type": "array"
}
},
"required": [
"items"
],
"type": "object"
},
"status": {
"description": "Read operation status using operationId.",
"type": "boolean"
},
"undo": {
"description": "Undo a completed correction within its Undo window.",
"type": "boolean"
}
},
"required": [
"operationId"
],
"type": "object"
},
"name": "fix_compliance",
"outputSchema": {
"additionalProperties": true,
"properties": {
"change": {
"additionalProperties": true,
"type": "object"
},
"confirmation": {
"additionalProperties": true,
"type": "object"
},
"finalAnswer": {
"type": "string"
},
"manifest": {
"additionalProperties": true,
"type": "object"
},
"message": {
"type": "string"
},
"operationId": {
"type": "string"
},
"preview": {
"additionalProperties": true,
"type": "object"
},
"requiresConfirmation": {
"type": "boolean"
},
"responseGuidance": {
"type": "string"
},
"reviewExpensesUrl": {
"type": "string"
},
"status": {
"type": "string"
},
"success": {
"type": "boolean"
},
"undo": {
"additionalProperties": true,
"type": "object"
}
},
"required": [
"success",
"status"
],
"type": "object"
}
},
{
"description": "Check whether an accounting destination is connected and read-only setup state for the owner. Returns the selected organization/company/business, stored connection state, rollout state, and the single setup link for connecting or reconnecting the destination. When an existing ExpenseBot report is available, pass reportId so the link opens that exact report's provider setup control instead of the general Reports setup. This is read-only and never posts accounting data. Agent-driven posting is currently available only for Zoho Books (zoho_books); QuickBooks Online (quickbooks), Xero (xero), Wave (wave), and FreeAgent (freeagent, beta) are accepted here for connection/setup status only and report writesAuthorized=false until their canonical planning path is shared.",
"inputSchema": {
"properties": {
"provider": {
"description": "Accounting destination provider. QuickBooks Online, Xero, Wave, FreeAgent (beta), or Zoho Books.",
"enum": [
"zoho_books",
"quickbooks",
"xero",
"wave",
"freeagent"
],
"type": "string"
},
"reportId": {
"description": "Optional existing ExpenseBot report ID. When supplied, setupUrl opens this exact report and highlights the selected accounting provider's setup control.",
"type": "string"
}
},
"required": [
"provider"
],
"type": "object"
},
"name": "get_accounting_integration_status",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"description": "Per-provider connection and rollout state.",
"type": "object"
},
"message": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Read the authoritative posting and reconciliation status for one ExpenseBot report at an accounting destination. Use after a timeout or uncertain response before considering any retry. Returns status, submission time, posting mode, posted receipt count, any public error, and whether reconciliation is required. Never re-post a completed report or a report marked needs_reconciliation; show that state to the owner instead. This tool is read-only and owner-account only.",
"inputSchema": {
"properties": {
"provider": {
"description": "Accounting destination provider used for the prior posting attempt.",
"enum": [
"zoho_books",
"quickbooks",
"xero",
"wave",
"freeagent"
],
"type": "string"
},
"reportId": {
"description": "Exact ExpenseBot report ID from the prior proposal or posting attempt.",
"type": "string"
}
},
"required": [
"provider",
"reportId"
],
"type": "object"
},
"name": "get_accounting_push_status",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
},
"message": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Read the same per-client advance balances shown in ExpenseBot's Client advances section. Use for questions like 'how much of Acme's advance remains?', 'which client floats are still open?', or 'do I owe a client a refund?'. A negative ledger balance means money remains to refund; a positive balance means the client owes the user. Returns the existing app handoff for Refund leftover or Bill Client. Read-only: never records a refund, creates an invoice, or recomputes the ledger in model prose.",
"inputSchema": {
"properties": {
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"clientName": {
"description": "Optional client name or canonical Client tag. Omit to list every open balance backed by a recorded client advance.",
"type": "string"
}
},
"type": "object"
},
"name": "get_client_advance_balances",
"outputSchema": {
"additionalProperties": true,
"properties": {
"balances": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"data": {
"additionalProperties": true,
"type": "object"
},
"homeCurrency": {
"type": "string"
},
"message": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Get the full financial and delivery summary for one issued client invoice, including subtotal, markup, tax, advance applied, balance due, dates, status, source report, and private Google Doc/PDF/DOCX links when available. Provide invoiceId or invoiceNumber — one is required; invoiceNumber is accepted only when it is unique. Read-only.",
"inputSchema": {
"properties": {
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"invoiceId": {
"description": "Stable invoice ID from list_client_invoices. Required unless invoiceNumber is supplied.",
"type": "string"
},
"invoiceNumber": {
"description": "Human invoice number when invoiceId is unavailable; it must identify one unique invoice.",
"type": "string"
}
},
"type": "object"
},
"name": "get_client_invoice",
"outputSchema": {
"additionalProperties": true,
"properties": {
"invoice": {
"additionalProperties": true,
"description": "The invoice record. Absent only on the error branch (not-found), which never carries structuredContent.",
"properties": {
"advanceApplied": {
"type": "number"
},
"balanceDue": {
"type": "number"
},
"billClientUrl": {
"type": [
"string",
"null"
]
},
"clientName": {
"type": [
"string",
"null"
]
},
"clientTag": {
"type": [
"string",
"null"
]
},
"currency": {
"type": "string"
},
"deliveryStatus": {
"type": "string"
},
"displayStatus": {
"description": "Derived: overdue is computed from status + dueDate, not stored.",
"enum": [
"open",
"overdue",
"paid",
"void",
"superseded",
"needs_review"
],
"type": "string"
},
"documentUrl": {
"type": [
"string",
"null"
]
},
"dueDate": {
"type": [
"string",
"null"
]
},
"grandTotal": {
"type": "number"
},
"invoiceId": {
"description": "Durable invoice identity.",
"type": "string"
},
"invoiceNumber": {
"type": [
"string",
"null"
]
},
"issueDate": {
"type": [
"string",
"null"
]
},
"paidAmount": {
"description": "null until paid.",
"type": [
"number",
"null"
]
},
"paidDate": {
"description": "null until paid.",
"type": [
"string",
"null"
]
},
"pdfUrl": {
"type": [
"string",
"null"
]
},
"reportUrl": {
"type": [
"string",
"null"
]
},
"status": {
"description": "Stored status, e.g. \"open\", \"paid\", \"void\".",
"type": "string"
},
"subtotal": {
"type": "number"
},
"taxAmount": {
"type": "number"
}
},
"type": "object"
},
"message": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "List card refunds, cashback/rewards, and statement credits that ExpenseBot has already recorded — either as negative expenses or matched against the original charge. Examples: 'did my refund come through', 'show my statement credits', 'was that return recorded'. Returns the most recent items (default 25, newest first); narrow with dateRange. Read-only: it never scans cards, changes review decisions, or adds rows.",
"inputSchema": {
"properties": {
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"dateRange": {
"additionalProperties": false,
"description": "Optional open-ended or closed window to narrow results. Supply startDate, endDate, or both; supplied bounds are inclusive YYYY-MM-DD dates.",
"properties": {
"endDate": {
"description": "Inclusive end date in YYYY-MM-DD format.",
"format": "date",
"type": "string"
},
"startDate": {
"description": "Inclusive start date in YYYY-MM-DD format.",
"format": "date",
"type": "string"
}
},
"type": "object"
},
"limit": {
"description": "Maximum results to return (default 25, max 100).",
"type": "number"
}
},
"type": "object"
},
"name": "get_credits_refunds",
"outputSchema": {
"additionalProperties": true,
"properties": {
"credits": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"data": {
"additionalProperties": true,
"type": "object"
},
"message": {
"type": "string"
},
"refunds": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Run deeper, multi-step analytics on the user's expenses. Use for explanatory questions like 'why did my spending increase' or 'compare Q1 vs Q2'. Takes 10-30 seconds (runs as a background job, polled automatically). Returns: { message, data: { ..., sampleMeta? } } where sampleMeta.isTruncated indicates whether the agent saw the full dataset.",
"inputSchema": {
"properties": {
"dateRange": {
"description": "Time period filter. Use exactly one variant — pick the shape that matches the user's phrasing.",
"oneOf": [
{
"additionalProperties": false,
"properties": {
"month": {
"description": "Month name or 1-12 (e.g., 'March' or '3')",
"type": "string"
},
"type": {
"const": "month",
"type": "string"
},
"year": {
"description": "4-digit year (e.g., 2026)",
"type": "integer"
}
},
"required": [
"type",
"month",
"year"
],
"title": "Specific month",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"quarter": {
"maximum": 4,
"minimum": 1,
"type": "integer"
},
"type": {
"const": "quarter",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"quarter",
"year"
],
"title": "Quarter of a year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "year",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"year"
],
"title": "Full year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"end": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"start": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"type": {
"const": "specific",
"type": "string"
}
},
"required": [
"type",
"start",
"end"
],
"title": "Specific date range",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "relative",
"type": "string"
},
"value": {
"description": "Relative range phrase, e.g., 'last month', 'YTD', 'this quarter', 'last 7 days'",
"type": "string"
}
},
"required": [
"type",
"value"
],
"title": "Relative natural-language range",
"type": "object"
}
]
},
"query": {
"description": "The analytics question to answer",
"type": "string"
}
},
"required": [
"query"
],
"type": "object"
},
"name": "get_deep_analytics",
"outputSchema": {
"additionalProperties": true,
"description": "Two branches. Async: type='analytics_pending' with jobId — poll poll_analytics until complete. Inline: message + data with the computed analytics (data may be null when there is no spreadsheet or no data yet). success is present only on the async branch.",
"properties": {
"data": {
"additionalProperties": true,
"description": "Inline branch: computed analytics, or null when no spreadsheet / no data.",
"type": [
"object",
"null"
]
},
"estimatedTime": {
"description": "Async branch only: seconds.",
"type": "number"
},
"jobId": {
"description": "Async branch only.",
"type": "string"
},
"message": {
"description": "Human-readable narrative (both branches).",
"type": "string"
},
"pollEndpoint": {
"description": "Async branch only.",
"type": "string"
},
"pollInterval": {
"description": "Async branch only: milliseconds.",
"type": "number"
},
"response": {
"description": "Chat alias of message.",
"type": "string"
},
"success": {
"description": "Async branch only.",
"type": "boolean"
},
"type": {
"description": "Async branch only: \"analytics_pending\" — poll pollEndpoint or call poll_analytics with jobId.",
"type": "string"
}
},
"required": [],
"type": "object"
}
},
{
"description": "Fetch a single expense row by its sheet row number or by its ExpenseBot expenseId (the exact Receipt ID in Column Q). Returns the row's headers + values + a labeled {header → value} map so you can refer to a specific expense the user mentioned. Read-only.",
"inputSchema": {
"properties": {
"expenseId": {
"description": "Exact full ExpenseBot Receipt ID from Column Q",
"type": "string"
},
"rowNumber": {
"description": "1-indexed sheet row (row 1 is headers, so ≥ 2)",
"minimum": 2,
"type": "integer"
}
},
"type": "object"
},
"name": "get_expense_by_id",
"outputSchema": {
"additionalProperties": true,
"properties": {
"expense": {
"additionalProperties": true,
"description": "The expense record.",
"type": "object"
},
"reviewExpenseUrl": {
"type": "string"
},
"rowNumber": {
"description": "Resolved sheet row for the expense.",
"type": "integer"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Show how an expense was split across categories, clients, properties, or business vs personal portions. Returns the single parent payment with its nested allocation lines — split lines are never counted as separate expenses, so totals stay correct. Examples: 'how is that expense split', 'what was the business portion of that bill', 'show the allocation for this receipt'. Read-only — splits are edited in ExpenseBot's Review workspace.",
"inputSchema": {
"properties": {
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"dateRange": {
"additionalProperties": false,
"description": "Optional open-ended or closed window to search for split expenses. Supply startDate, endDate, or both; supplied bounds are inclusive YYYY-MM-DD dates.",
"properties": {
"endDate": {
"description": "Inclusive end date in YYYY-MM-DD format.",
"format": "date",
"type": "string"
},
"startDate": {
"description": "Inclusive start date in YYYY-MM-DD format.",
"format": "date",
"type": "string"
}
},
"type": "object"
},
"limit": {
"description": "Max parent expenses to return (default 25, max 100).",
"type": "number"
},
"onlySplit": {
"description": "Return only expenses with active splits (default true). Set false to inspect an unsplit Receipt ID.",
"type": "boolean"
},
"receiptId": {
"description": "Optional Receipt ID for one expense.",
"type": "string"
}
},
"type": "object"
},
"name": "get_expense_splits",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
},
"message": {
"type": "string"
},
"splits": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Get income totals, breakdowns, and analytics from the Income tab. Covers Schedule C / T2125 income, Schedule B drill-in (interest, dividends, tax refunds, security deposits), rental income, and per-source / per-payment-method / per-category / per-month / per-tag breakdowns. Schedule-C-style category exclusions match year-end T6 routing (security deposits, refunds excluded from taxable totals). Examples: 'income YTD', 'income by source', 'rental income by property', 'interest income this year', 'dividends YTD', 'tax refunds 2024', 'income this year vs last' (YoY). Supports period comparison phrasing — YoY ('vs last year'), MoM ('vs last month'), QoQ ('Q1 vs Q2'), same-month-prev-year. Returns: { message, data: { total, breakdown?, comparison?, sampleMeta? } }.",
"inputSchema": {
"properties": {
"dateRange": {
"description": "Time period filter. Use exactly one variant — pick the shape that matches the user's phrasing.",
"oneOf": [
{
"additionalProperties": false,
"properties": {
"month": {
"description": "Month name or 1-12 (e.g., 'March' or '3')",
"type": "string"
},
"type": {
"const": "month",
"type": "string"
},
"year": {
"description": "4-digit year (e.g., 2026)",
"type": "integer"
}
},
"required": [
"type",
"month",
"year"
],
"title": "Specific month",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"quarter": {
"maximum": 4,
"minimum": 1,
"type": "integer"
},
"type": {
"const": "quarter",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"quarter",
"year"
],
"title": "Quarter of a year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "year",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"year"
],
"title": "Full year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"end": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"start": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"type": {
"const": "specific",
"type": "string"
}
},
"required": [
"type",
"start",
"end"
],
"title": "Specific date range",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "relative",
"type": "string"
},
"value": {
"description": "Relative range phrase, e.g., 'last month', 'YTD', 'this quarter', 'last 7 days'",
"type": "string"
}
},
"required": [
"type",
"value"
],
"title": "Relative natural-language range",
"type": "object"
}
]
},
"groupBy": {
"description": "How to group the breakdown",
"enum": [
"source",
"category",
"month",
"paymentMethod",
"tag"
],
"type": "string"
},
"incomeTagPrefix": {
"description": "Optional tag-prefix shortcut (e.g., 'Prop –' for rental income, 'Client –' for client billings, 'Wedding –' for events). When set, the tool filters to income rows tagged with this prefix.",
"type": "string"
},
"query": {
"description": "Natural language income question (e.g., 'income YTD by source', 'rental income last month')",
"type": "string"
}
},
"type": "object"
},
"name": "get_income_summary",
"outputSchema": {
"additionalProperties": true,
"description": "Two branches. Async: type='analytics_pending' with jobId — poll poll_analytics until complete. Inline: message + data with the computed analytics (data may be null when there is no spreadsheet or no data yet). success is present only on the async branch.",
"properties": {
"data": {
"additionalProperties": true,
"description": "Inline branch: computed analytics, or null when no spreadsheet / no data.",
"type": [
"object",
"null"
]
},
"estimatedTime": {
"description": "Async branch only: seconds.",
"type": "number"
},
"jobId": {
"description": "Async branch only.",
"type": "string"
},
"message": {
"description": "Human-readable narrative (both branches).",
"type": "string"
},
"pollEndpoint": {
"description": "Async branch only.",
"type": "string"
},
"pollInterval": {
"description": "Async branch only: milliseconds.",
"type": "number"
},
"response": {
"description": "Chat alias of message.",
"type": "string"
},
"success": {
"description": "Async branch only.",
"type": "boolean"
},
"type": {
"description": "Async branch only: \"analytics_pending\" — poll pollEndpoint or call poll_analytics with jobId.",
"type": "string"
}
},
"required": [],
"type": "object"
}
},
{
"description": "Check the authoritative final outcome of a receipt image/PDF batch submitted with submit_receipt. After submit_receipt returns submissionId, call this tool with that exact ID using the polling interval and time allowance in the submit_receipt response. A small batch allows at least 5 minutes; larger batches allow longer. A pending result is normal and must not trigger a duplicate resubmission. Returns added, duplicate, skipped, held_for_review, or errored verdicts with exact counts and up to 10 processed receipt summaries. A held_for_review file (withheld for missing details or a possible duplicate) was not added; relay the next step in the message. found:false is pending and has no terminal verdict; found:true is terminal. On completion, report the authoritative per-file outcomes. Show spreadsheetUrl and reviewExpensesUrl for rows actually added, not as the destination for held files. For a held file relay only its message's next step (such as Files to review on Add Expenses); do not invent a missing card or direct held uploads to Gmail Action Needed. Unknown outcomes stay unknown; do not blindly resubmit. This is for uploaded receipts; use get_scan_status for Gmail scans. Read-only.",
"inputSchema": {
"properties": {
"submissionId": {
"description": "Exact submissionId returned by submit_receipt. Strongly preferred because it binds the result to this upload instead of an earlier receipt.",
"type": "string"
},
"withinSeconds": {
"default": 600,
"description": "Fallback lookback window when submissionId is unavailable (30-3600 seconds; default 600).",
"maximum": 3600,
"minimum": 30,
"type": "integer"
}
},
"type": "object"
},
"name": "get_last_receipt_result",
"outputSchema": {
"additionalProperties": true,
"allOf": [
{
"if": {
"properties": {
"found": {
"const": true
}
},
"required": [
"found"
]
},
"then": {
"required": [
"verdict",
"counts",
"receipts",
"submissionId",
"spreadsheetUrl",
"reviewExpensesUrl",
"data",
"timestamp"
]
}
}
],
"description": "found:false means processing is still pending; found:true is a terminal result.",
"properties": {
"counts": {
"additionalProperties": true,
"properties": {
"added": {
"minimum": 0,
"type": "integer"
},
"duplicates": {
"minimum": 0,
"type": "integer"
},
"errors": {
"minimum": 0,
"type": "integer"
},
"heldForReview": {
"description": "Files held for review and not added. Present only when at least one.",
"minimum": 0,
"type": "integer"
},
"skipped": {
"minimum": 0,
"type": "integer"
},
"submitted": {
"minimum": 0,
"type": "integer"
}
},
"required": [
"submitted",
"added",
"duplicates",
"skipped",
"errors"
],
"type": "object"
},
"data": {
"additionalProperties": true,
"type": "object"
},
"found": {
"type": "boolean"
},
"message": {
"type": "string"
},
"receipts": {
"description": "Processed receipt summaries. Held files are not listed here; see data.outcomes.",
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"reviewExpensesUrl": {
"type": "string"
},
"spreadsheetUrl": {
"type": [
"string",
"null"
]
},
"state": {
"const": "processing",
"description": "Present on pending polls for a specific submission.",
"type": "string"
},
"submissionId": {
"type": [
"string",
"null"
]
},
"success": {
"const": true,
"type": "boolean"
},
"timestamp": {
"format": "date-time",
"type": [
"string",
"null"
]
},
"verdict": {
"description": "Terminal branch only. held_for_review: every file was held for review, so none was added. mixed also covers any batch that includes held files.",
"enum": [
"added",
"duplicate",
"skipped",
"errored",
"mixed",
"unknown",
"held_for_review"
],
"type": "string"
}
},
"required": [
"success",
"found",
"message"
],
"type": "object"
}
},
{
"description": "Mileage analytics — totals, breakdowns by month / client / purpose / category, plus deduction framing (cents-per-mile or cents-per-km × distance, country-aware IRS / CRA rates). Examples: 'mileage this year', 'miles driven for Acme', 'mileage by month', 'mileage deduction estimate', 'business miles last quarter'. Supports YoY / MoM / QoQ comparison phrasing. Returns: { message, data: { totalDistance, deductionEstimate?, breakdown?, comparison?, sampleMeta? } }.",
"inputSchema": {
"properties": {
"dateRange": {
"description": "Time period filter. Use exactly one variant — pick the shape that matches the user's phrasing.",
"oneOf": [
{
"additionalProperties": false,
"properties": {
"month": {
"description": "Month name or 1-12 (e.g., 'March' or '3')",
"type": "string"
},
"type": {
"const": "month",
"type": "string"
},
"year": {
"description": "4-digit year (e.g., 2026)",
"type": "integer"
}
},
"required": [
"type",
"month",
"year"
],
"title": "Specific month",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"quarter": {
"maximum": 4,
"minimum": 1,
"type": "integer"
},
"type": {
"const": "quarter",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"quarter",
"year"
],
"title": "Quarter of a year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "year",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"year"
],
"title": "Full year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"end": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"start": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"type": {
"const": "specific",
"type": "string"
}
},
"required": [
"type",
"start",
"end"
],
"title": "Specific date range",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "relative",
"type": "string"
},
"value": {
"description": "Relative range phrase, e.g., 'last month', 'YTD', 'this quarter', 'last 7 days'",
"type": "string"
}
},
"required": [
"type",
"value"
],
"title": "Relative natural-language range",
"type": "object"
}
]
},
"groupBy": {
"description": "How to group the breakdown",
"enum": [
"month",
"tag",
"purpose",
"category"
],
"type": "string"
},
"query": {
"description": "Natural language mileage question (e.g., 'mileage this year', 'miles driven for Acme client')",
"type": "string"
}
},
"type": "object"
},
"name": "get_mileage_summary",
"outputSchema": {
"additionalProperties": true,
"description": "Success returns message + data with the computed summary; success is not emitted on the inline analytics path.",
"properties": {
"data": {
"additionalProperties": true,
"type": [
"object",
"null"
]
},
"message": {
"type": "string"
},
"success": {
"description": "Present only on the error branch (out of scope) / async path.",
"type": "boolean"
}
},
"required": [],
"type": "object"
}
},
{
"description": "Month-end summary of the user's books for one calendar month: income recorded, money spent, net, top spending categories and merchants, plus alerts for anything unusual that month. Examples: 'how did last month go', 'close out my books for June', 'monthly review', 'what did I make and spend in May'. Defaults to the last completed month. Figures come from the user's own recorded data; advisory notes are estimates, not tax advice.",
"inputSchema": {
"properties": {
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"period": {
"description": "Calendar month in YYYY-MM format. Defaults to the last completed month.",
"pattern": "^\\d{4}-\\d{2}$",
"type": "string"
}
},
"type": "object"
},
"name": "get_monthly_books_review",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
},
"message": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Per-tag P&L — revenue, cost, profit, and margin grouped by tag (per-client, per-property, per-event, per-realtor-deal). Requires both income AND expense rows to be tagged with matching labels. Common tag-prefix shortcuts: 'Prop –' (rentals), 'Client –' (client billings), 'Wedding –' (events), 'Realtor –' (real estate deals). Examples: 'per-tag P&L this year', 'profit by client', 'profit by property', 'profit on the Smith wedding', 'per-client P&L this year vs last' (YoY). Supports YoY / MoM / QoQ comparison phrasing. Margin renders as multiplier in loss territory. Defaults to year-to-date if no date range given.",
"inputSchema": {
"properties": {
"dateRange": {
"description": "Time period filter. Use exactly one variant — pick the shape that matches the user's phrasing.",
"oneOf": [
{
"additionalProperties": false,
"properties": {
"month": {
"description": "Month name or 1-12 (e.g., 'March' or '3')",
"type": "string"
},
"type": {
"const": "month",
"type": "string"
},
"year": {
"description": "4-digit year (e.g., 2026)",
"type": "integer"
}
},
"required": [
"type",
"month",
"year"
],
"title": "Specific month",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"quarter": {
"maximum": 4,
"minimum": 1,
"type": "integer"
},
"type": {
"const": "quarter",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"quarter",
"year"
],
"title": "Quarter of a year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "year",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"year"
],
"title": "Full year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"end": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"start": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"type": {
"const": "specific",
"type": "string"
}
},
"required": [
"type",
"start",
"end"
],
"title": "Specific date range",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "relative",
"type": "string"
},
"value": {
"description": "Relative range phrase, e.g., 'last month', 'YTD', 'this quarter', 'last 7 days'",
"type": "string"
}
},
"required": [
"type",
"value"
],
"title": "Relative natural-language range",
"type": "object"
}
]
},
"query": {
"description": "Natural language per-tag P&L question",
"type": "string"
},
"tagPrefix": {
"description": "Optional prefix to limit which tags are bucketed (e.g., 'Prop –' for properties only, 'Client –' for clients only). When omitted, all tags are included.",
"type": "string"
}
},
"type": "object"
},
"name": "get_per_tag_pnl",
"outputSchema": {
"additionalProperties": true,
"description": "Two branches. Async: type='analytics_pending' with jobId — poll poll_analytics until complete. Inline: message + data with the computed analytics (data may be null when there is no spreadsheet or no data yet). success is present only on the async branch.",
"properties": {
"data": {
"additionalProperties": true,
"description": "Inline branch: computed analytics, or null when no spreadsheet / no data.",
"type": [
"object",
"null"
]
},
"estimatedTime": {
"description": "Async branch only: seconds.",
"type": "number"
},
"jobId": {
"description": "Async branch only.",
"type": "string"
},
"message": {
"description": "Human-readable narrative (both branches).",
"type": "string"
},
"pollEndpoint": {
"description": "Async branch only.",
"type": "string"
},
"pollInterval": {
"description": "Async branch only: milliseconds.",
"type": "number"
},
"response": {
"description": "Chat alias of message.",
"type": "string"
},
"success": {
"description": "Async branch only.",
"type": "boolean"
},
"type": {
"description": "Async branch only: \"analytics_pending\" — poll pollEndpoint or call poll_analytics with jobId.",
"type": "string"
}
},
"required": [],
"type": "object"
}
},
{
"description": "Compute Profit & Loss (P&L / net income / margin) by combining the Income tab with expense tabs. Examples: 'am I profitable this year', 'P&L for Q1', 'net income last quarter', 'what's my margin', 'P&L this year vs last' (YoY). Supports period comparison — YoY, MoM, QoQ, same-month-prev-year. Margin renders as multiplier in loss territory ('expenses 5.4× revenue') so the user gets a readable signal instead of '-436.9% margin'. Returns: { message, data: { revenue, expenses, netIncome, margin, comparison?, sampleMeta? } }.",
"inputSchema": {
"properties": {
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"dateRange": {
"description": "Time period filter. Use exactly one variant — pick the shape that matches the user's phrasing.",
"oneOf": [
{
"additionalProperties": false,
"properties": {
"month": {
"description": "Month name or 1-12 (e.g., 'March' or '3')",
"type": "string"
},
"type": {
"const": "month",
"type": "string"
},
"year": {
"description": "4-digit year (e.g., 2026)",
"type": "integer"
}
},
"required": [
"type",
"month",
"year"
],
"title": "Specific month",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"quarter": {
"maximum": 4,
"minimum": 1,
"type": "integer"
},
"type": {
"const": "quarter",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"quarter",
"year"
],
"title": "Quarter of a year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "year",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"year"
],
"title": "Full year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"end": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"start": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"type": {
"const": "specific",
"type": "string"
}
},
"required": [
"type",
"start",
"end"
],
"title": "Specific date range",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "relative",
"type": "string"
},
"value": {
"description": "Relative range phrase, e.g., 'last month', 'YTD', 'this quarter', 'last 7 days'",
"type": "string"
}
},
"required": [
"type",
"value"
],
"title": "Relative natural-language range",
"type": "object"
}
]
},
"query": {
"description": "Natural language P&L question (e.g., 'P&L for Q1 2025', 'am I profitable')",
"type": "string"
}
},
"type": "object"
},
"name": "get_pnl",
"outputSchema": {
"additionalProperties": true,
"description": "Two branches. Async: type='analytics_pending' with jobId — poll poll_analytics until complete. Inline: message + data with the computed analytics (data may be null when there is no spreadsheet or no data yet). success is present only on the async branch.",
"properties": {
"data": {
"additionalProperties": true,
"description": "Inline branch: computed analytics, or null when no spreadsheet / no data.",
"type": [
"object",
"null"
]
},
"estimatedTime": {
"description": "Async branch only: seconds.",
"type": "number"
},
"jobId": {
"description": "Async branch only.",
"type": "string"
},
"message": {
"description": "Human-readable narrative (both branches).",
"type": "string"
},
"pollEndpoint": {
"description": "Async branch only.",
"type": "string"
},
"pollInterval": {
"description": "Async branch only: milliseconds.",
"type": "number"
},
"response": {
"description": "Chat alias of message.",
"type": "string"
},
"success": {
"description": "Async branch only.",
"type": "boolean"
},
"type": {
"description": "Async branch only: \"analytics_pending\" — poll pollEndpoint or call poll_analytics with jobId.",
"type": "string"
}
},
"required": [],
"type": "object"
}
},
{
"description": "Show what the user (or their AI assistants) has recently done in ExpenseBot via this MCP server: which tools were called, when, with what arguments, and whether they succeeded. This is a log of assistant TOOL CALLS, not the processing history of a document. Useful for questions like 'what did I do this week' or 'which tools has my assistant run', and to give the user transparency into AI-assisted actions. Returns the most recent N entries from the audit log (default 20, max 100).",
"inputSchema": {
"properties": {
"actionId": {
"description": "Optional: filter to a single tool/action name",
"type": "string"
},
"limit": {
"default": 20,
"maximum": 100,
"minimum": 1,
"type": "integer"
},
"sinceDays": {
"default": 7,
"description": "Only show actions from the last N days (default 7)",
"type": "integer"
}
},
"type": "object"
},
"name": "get_recent_activity",
"outputSchema": {
"additionalProperties": true,
"properties": {
"actions": {
"items": {
"additionalProperties": true,
"properties": {
"actionId": {
"type": "string"
},
"durationMs": {
"type": "number"
},
"success": {
"type": "boolean"
},
"timestamp": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"count": {
"type": "integer"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"count",
"actions"
],
"type": "object"
}
},
{
"description": "Get full details of a specific expense report including all expenses, totals, and compliance status. Use only when the user explicitly asks to inspect an existing report's details. Never call this as a preflight or follow-up to creating, sharing, or billing from a report; those tools already return the required result and links.",
"inputSchema": {
"properties": {
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"reportId": {
"description": "The report/spreadsheet ID",
"type": "string"
},
"userRequest": {
"description": "The user's exact request asking to inspect this report. Do not paraphrase or invent diagnostic intent.",
"type": "string"
}
},
"required": [
"reportId",
"userRequest"
],
"type": "object"
},
"name": "get_report_details",
"outputSchema": {
"additionalProperties": true,
"properties": {
"report": {
"additionalProperties": true,
"description": "The full stored report record.",
"type": "object"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"report"
],
"type": "object"
}
},
{
"description": "Check the authoritative status of the user's Gmail receipt scans. Returns the active scan lock, the same live phase and item progress shown by ExpenseBot's in-app status pill, queued or attention-needed years, completed calendar years, current merchant/category exclusions, and recent outcomes. Call when the user asks whether a scan is running, finished, stuck, or what it is doing. When complete, show the returned spreadsheetUrl or reviewExpensesUrl; when setup or reconnection is needed, show gmailScanUrl. Read-only.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "get_scan_status",
"outputSchema": {
"additionalProperties": true,
"description": "Authoritative live scan state. Fields may sit at the top level or under data depending on the branch; nothing beyond success is guaranteed, so treat each as optional.",
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
},
"gmailScanUrl": {
"type": [
"string",
"null"
]
},
"latestScan": {
"additionalProperties": true,
"description": "Most recent scan outcome (success, emailsFound, emailsTagged, timestamp).",
"type": [
"object",
"null"
]
},
"lock": {
"additionalProperties": true,
"description": "Scan-lock state (active, startedAt, ageSeconds, dispatchComplete, pendingProcessingCount).",
"type": "object"
},
"message": {
"type": "string"
},
"reviewExpensesUrl": {
"type": [
"string",
"null"
]
},
"scanInProgress": {
"type": "boolean"
},
"spreadsheetUrl": {
"type": [
"string",
"null"
]
},
"status": {
"description": "Scan lifecycle state.",
"type": [
"string",
"null"
]
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Summarize the user's recorded expenses with totals and breakdowns by category, merchant, month, tag, source, or payment method. Supports date ranges, period comparisons, and total, count, or average metrics. Read-only. Returns: { message, data: { total, breakdown?, comparison?, sampleMeta? } }.",
"inputSchema": {
"properties": {
"categories": {
"description": "Optional configured expense categories to include in the summary.",
"items": {
"type": "string"
},
"type": "array"
},
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"dateRange": {
"description": "Time period filter. Use exactly one variant — pick the shape that matches the user's phrasing.",
"oneOf": [
{
"additionalProperties": false,
"properties": {
"month": {
"description": "Month name or 1-12 (e.g., 'March' or '3')",
"type": "string"
},
"type": {
"const": "month",
"type": "string"
},
"year": {
"description": "4-digit year (e.g., 2026)",
"type": "integer"
}
},
"required": [
"type",
"month",
"year"
],
"title": "Specific month",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"quarter": {
"maximum": 4,
"minimum": 1,
"type": "integer"
},
"type": {
"const": "quarter",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"quarter",
"year"
],
"title": "Quarter of a year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "year",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"year"
],
"title": "Full year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"end": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"start": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"type": {
"const": "specific",
"type": "string"
}
},
"required": [
"type",
"start",
"end"
],
"title": "Specific date range",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "relative",
"type": "string"
},
"value": {
"description": "Relative range phrase, e.g., 'last month', 'YTD', 'this quarter', 'last 7 days'",
"type": "string"
}
},
"required": [
"type",
"value"
],
"title": "Relative natural-language range",
"type": "object"
}
]
},
"groupBy": {
"enum": [
"category",
"merchant",
"month",
"tag",
"source",
"paymentMethod"
],
"type": "string"
},
"metric": {
"enum": [
"total",
"count",
"average"
],
"type": "string"
},
"query": {
"description": "Natural language question (e.g., 'how much did I spend in March', 'top merchants this quarter')",
"type": "string"
}
},
"type": "object"
},
"name": "get_spending_summary",
"outputSchema": {
"additionalProperties": true,
"description": "Two branches. Async: type='analytics_pending' with jobId — poll poll_analytics until complete. Inline: message + data with the computed analytics (data may be null when there is no spreadsheet or no data yet). success is present only on the async branch.",
"properties": {
"data": {
"additionalProperties": true,
"description": "Inline branch: computed analytics, or null when no spreadsheet / no data.",
"type": [
"object",
"null"
]
},
"estimatedTime": {
"description": "Async branch only: seconds.",
"type": "number"
},
"jobId": {
"description": "Async branch only.",
"type": "string"
},
"message": {
"description": "Human-readable narrative (both branches).",
"type": "string"
},
"pollEndpoint": {
"description": "Async branch only.",
"type": "string"
},
"pollInterval": {
"description": "Async branch only: milliseconds.",
"type": "number"
},
"response": {
"description": "Chat alias of message.",
"type": "string"
},
"success": {
"description": "Async branch only.",
"type": "boolean"
},
"type": {
"description": "Async branch only: \"analytics_pending\" — poll pollEndpoint or call poll_analytics with jobId.",
"type": "string"
}
},
"required": [],
"type": "object"
}
},
{
"description": "Return the user's master ExpenseBot Google Sheet plus authenticated ExpenseBot workspace links, each with a label and a description of when to use it. Use this when the user asks to open, view, check, or edit their spreadsheet; review expenses or income; manually scan Gmail; reconcile; connect or manage a bank/credit card; open Automation Hub or General Settings; create or open reports; or asks where a submitted receipt went. Choose and show the one or two links relevant to the request instead of listing the entire catalog. After a receipt submission, prefer Review expenses plus the Google Sheet. After an income write, prefer Review income plus the Sheet. For a Gmail scan or connection request, use the Scan Gmail link, which opens the existing Gmail scanning interface. Bank/card requests use the Reconcile link; configuration requests use the returned Automation Hub or General Settings link. Category, G/L, and account-code requests use Category management; client, project, and trip groups use Group management; questions about what counts as Personal use Personal rules. For client billing setup, use the returned clientBillingHandoffs instead of giving a generic dashboard tour: manageClients creates or manages the client list, tagReadyExpenses opens Review Expenses filtered to blank or default-Business rows, and createClientReport opens report creation by group after expenses have been assigned. Choose the one next step that matches the user's current state. If the user names a client and an exact expense scope and asks the assistant to do the work, use search_expenses and group_expenses instead; show its grounded preview and require explicit confirmation before changing any rows. When the user asks to choose receipt images from Google Photos, use the returned Google Photos link; it opens the existing authenticated picker and handles any required Google consent in ExpenseBot. When they ask where ExpenseBot stores receipt files, return the Drive folder link only when it is available; never invent or request a folder ID. For a complete, unambiguous cash expense or mileage trip, use the direct write tool. Use the returned Manual expense link for multiple entries, credits/refunds, detailed tax/tip entry, per diem, incomplete details, or visual review. Use the Mileage and travel link for route calculation, mileage settings, repeated trips, calendar/rideshare imports, per diem, or visual review. These links open the existing reviewed app forms. When the user wants to import income from a screenshot, PDF, or CSV but no attachment is available in chat (or the review is visually complex), use the returned Import income links — they open the existing Add Income importer, which parses the file and shows every row for review before anything is saved. Team setup requests use the returned role-aware Team setup handoff: eligible solo business owners open Add teammates, while existing team owners and co-admins open Team Management. If the handoff says owner access is required, explain that instead of presenting a broken link. Complex or visually reviewed report requests use the returned Create report link. Read-only; this tool does not create or modify spreadsheet rows.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "get_spreadsheet_url",
"outputSchema": {
"additionalProperties": true,
"properties": {
"dashboardUrl": {
"type": "string"
},
"data": {
"additionalProperties": true,
"type": "object"
},
"gmailActionNeededUrl": {
"type": "string"
},
"gmailScanUrl": {
"type": "string"
},
"message": {
"type": "string"
},
"reportsUrl": {
"type": "string"
},
"reviewExpensesUrl": {
"type": "string"
},
"reviewIncomeUrl": {
"type": "string"
},
"spreadsheetId": {
"minLength": 1,
"type": "string"
},
"spreadsheetUrl": {
"type": "string"
},
"success": {
"const": true,
"type": "boolean"
}
},
"required": [
"success",
"spreadsheetId",
"spreadsheetUrl",
"reviewExpensesUrl",
"reviewIncomeUrl",
"gmailScanUrl",
"gmailActionNeededUrl",
"reportsUrl",
"dashboardUrl",
"data",
"message"
],
"type": "object"
}
},
{
"description": "Subscription audit — wraps the Subscription Auditor engine to find recurring charges, duplicates, price increases, and trial-conversion suspects in the user's expenses. Examples: 'recurring subscriptions', 'duplicate subscriptions', 'price increases', 'trial conversions', 'subscriptions over $20/month'. Returns: { message, data: { recurring, duplicates, priceIncreases, trialConversions, totalMonthlyCost, sampleMeta? } }.",
"inputSchema": {
"properties": {
"dateRange": {
"description": "Time period filter. Use exactly one variant — pick the shape that matches the user's phrasing.",
"oneOf": [
{
"additionalProperties": false,
"properties": {
"month": {
"description": "Month name or 1-12 (e.g., 'March' or '3')",
"type": "string"
},
"type": {
"const": "month",
"type": "string"
},
"year": {
"description": "4-digit year (e.g., 2026)",
"type": "integer"
}
},
"required": [
"type",
"month",
"year"
],
"title": "Specific month",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"quarter": {
"maximum": 4,
"minimum": 1,
"type": "integer"
},
"type": {
"const": "quarter",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"quarter",
"year"
],
"title": "Quarter of a year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "year",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"year"
],
"title": "Full year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"end": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"start": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"type": {
"const": "specific",
"type": "string"
}
},
"required": [
"type",
"start",
"end"
],
"title": "Specific date range",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "relative",
"type": "string"
},
"value": {
"description": "Relative range phrase, e.g., 'last month', 'YTD', 'this quarter', 'last 7 days'",
"type": "string"
}
},
"required": [
"type",
"value"
],
"title": "Relative natural-language range",
"type": "object"
}
]
},
"focus": {
"description": "Narrow the audit to a single category (default: all)",
"enum": [
"recurring",
"duplicates",
"price_increases",
"trial_conversions",
"all"
],
"type": "string"
},
"query": {
"description": "Natural language subscription question (e.g., 'find duplicate subscriptions', 'price increases this year')",
"type": "string"
}
},
"type": "object"
},
"name": "get_subscription_audit",
"outputSchema": {
"additionalProperties": true,
"description": "Success returns message + data with the computed summary; success is not emitted on the inline analytics path.",
"properties": {
"data": {
"additionalProperties": true,
"type": [
"object",
"null"
]
},
"message": {
"type": "string"
},
"success": {
"description": "Present only on the error branch (out of scope) / async path.",
"type": "boolean"
}
},
"required": [],
"type": "object"
}
},
{
"description": "Show ExpenseBot's current high-confidence Trip Intelligence proposals from Review Expenses. Read-only: never groups, tags, creates reports, or writes to the spreadsheet. Each proposal returns strict YYYY-MM-DD dates, evidence, locations, and the exact Receipt IDs with expected current tags. Do not infer a client/project from geography. When nextAction is present, use exactly its group_expenses params with confirm:false, show the canonical preview, then ask for explicit confirmation before any write. Never send confirm:true from this read result alone. In accountant clientEmail mode this tool can read accepted clients but may return no executable nextAction; hand off to Review Expenses or the owner's own assistant connection to apply.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"limit": {
"description": "Maximum high-confidence proposals to return (default 3).",
"maximum": 5,
"minimum": 1,
"type": "integer"
}
},
"type": "object"
},
"name": "get_trip_suggestions",
"outputSchema": {
"additionalProperties": true,
"properties": {
"count": {
"type": "integer"
},
"message": {
"type": "string"
},
"proposals": {
"description": "Read-only trip proposals; accepting one continues through group_expenses.",
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Group an exact set of recorded expenses for a client, project, trip, job, or other user-named purpose. Examples: 'group my Mexico meals for client Rob', 'put these Vegas expenses under the Vegas project', or 'group these for client Rob and create a report'. The user does not need to know about tags: ExpenseBot resolves the requested name against existing groups and proposes creating one only when needed. Use exact expenseId values returned by search_expenses. A grounded preview is automatic for every bulk request; the user does not need to ask for one. First call with confirm omitted/false, show the returned exact rows, count, totals, proposed group, exclusions, and conflicts, then ask for approval. Only after explicit user approval, repeat the same operationId and selection with confirm:true. A premature confirm:true is converted to preview. When the request includes a report, set createReport:true on that confirmed group_expenses call and use its report result; do not run a separate broader create_report query. For 'without personal expenses', set excludePersonal:true; ExpenseBot removes Personal-tagged and Personal-category rows before preview so their Personal marker is never overwritten. The confirmed operation returns exact report and Bill Client links. Expenses already assigned to another ordinary report are excluded; if none remain, no duplicate report is created and the result links to Reports instead. Keep the user-facing response concise and do not add unsolicited tax or substantiation advice. Do not call check_compliance, get_report_details, or tax/deductibility tools before or after this workflow unless the user explicitly asks for that separate analysis. In user-facing prose call the destination a group, not a tag. Treat preview totals as provisional; after confirmation use only the terminal result's exact count, total, and currency without reconciling it against the preview. A returned Bill Client URL opens the reviewed billing handoff and does not mean an invoice was created, sent, or confirmed. Sharing remains a separate share_report action with recipient confirmation.",
"inputSchema": {
"additionalProperties": false,
"properties": {
"approveNewGroup": {
"description": "True only when the preview says a new group is required.",
"type": "boolean"
},
"confirm": {
"description": "Omit/false for preview; true only after explicit approval.",
"type": "boolean"
},
"createReport": {
"description": "Create a report from the exact eligible grouped expenses.",
"type": "boolean"
},
"excludePersonal": {
"description": "Exclude Personal-tagged and Personal-category expenses from both grouping and the exact report.",
"type": "boolean"
},
"excludedCategories": {
"description": "Expense categories to exclude before preview and grouping.",
"items": {
"type": "string"
},
"type": "array"
},
"excludedTags": {
"description": "Existing groups to exclude before preview and grouping.",
"items": {
"type": "string"
},
"type": "array"
},
"group": {
"additionalProperties": false,
"description": "The user-named destination group to apply to every eligible selected expense.",
"properties": {
"kind": {
"description": "How to label the destination in user-facing confirmation text.",
"enum": [
"client",
"project",
"trip",
"job",
"group"
],
"type": "string"
},
"name": {
"description": "The group name in the user's own words.",
"type": "string"
}
},
"required": [
"kind",
"name"
],
"type": "object"
},
"operationId": {
"description": "Stable idempotency key generated once for the preview and reused unchanged.",
"pattern": "^expense-bulk:[a-zA-Z0-9_-]{8,100}$",
"type": "string"
},
"reportTitle": {
"description": "Optional title when createReport is true; otherwise ExpenseBot derives one from the group.",
"type": "string"
},
"selection": {
"additionalProperties": false,
"description": "Exact expenses returned by search_expenses and approved for this bounded operation.",
"properties": {
"items": {
"description": "One exact receipt identity per selected expense; never broaden this list after preview.",
"items": {
"additionalProperties": false,
"properties": {
"expected": {
"additionalProperties": false,
"description": "Optional current state copied from search results for conflict detection.",
"properties": {
"tag": {
"description": "Current group/tag value observed before preview; an empty string means ungrouped.",
"type": "string"
}
},
"type": "object"
},
"receiptId": {
"description": "Exact full expenseId/Column-Q receipt identity.",
"type": "string"
}
},
"required": [
"receiptId"
],
"type": "object"
},
"maxItems": 500,
"minItems": 1,
"type": "array"
}
},
"required": [
"items"
],
"type": "object"
},
"status": {
"description": "Read operation status using operationId.",
"type": "boolean"
},
"undo": {
"description": "Undo a completed grouping within its undo window.",
"type": "boolean"
}
},
"required": [
"operationId"
],
"type": "object"
},
"name": "group_expenses",
"outputSchema": {
"additionalProperties": true,
"properties": {
"candidates": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"confirmation": {
"additionalProperties": true,
"type": "object"
},
"creationStatus": {
"type": "string"
},
"exclusions": {
"additionalProperties": true,
"type": "object"
},
"finalAnswer": {
"type": "string"
},
"group": {
"additionalProperties": true,
"type": "object"
},
"manifest": {
"additionalProperties": true,
"type": "object"
},
"message": {
"type": "string"
},
"operationId": {
"type": "string"
},
"preview": {
"additionalProperties": true,
"type": "object"
},
"report": {
"additionalProperties": true,
"type": "object"
},
"reportsUrl": {
"type": "string"
},
"requiresConfirmation": {
"type": "boolean"
},
"requiresNewGroupApproval": {
"type": "boolean"
},
"responseGuidance": {
"type": "string"
},
"reviewExpensesUrl": {
"type": "string"
},
"status": {
"type": "string"
},
"success": {
"type": "boolean"
},
"suggestedTag": {
"type": "string"
},
"undo": {
"additionalProperties": true,
"type": "object"
}
},
"required": [
"success",
"status"
],
"type": "object"
}
},
{
"description": "List the user's configured expense categories and billing accounts. Use this BEFORE calling tools that need a category (add_cash_expense, add_mileage_entry, create_report) so you pass the user's actual category names, not guesses. Returns the live list from their spreadsheet.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "list_categories",
"outputSchema": {
"additionalProperties": true,
"properties": {
"billingAccounts": {
"description": "Billing/card accounts, when configured.",
"items": {
"type": "string"
},
"type": "array"
},
"categories": {
"description": "Expense categories for the user.",
"items": {
"type": "string"
},
"type": "array"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"categories"
],
"type": "object"
}
},
{
"description": "List the user's issued client invoices (accounts receivable) — who owes them money, how much, and when it is due. Examples: 'which invoices are outstanding', 'what does Acme still owe me', 'any overdue invoices', 'how much am I waiting to get paid'. status accepts 'active' (default), 'open', 'overdue', 'needs_review', 'paid', 'void', 'superseded', or 'all'. Returns invoice numbers, status, totals by currency, delivery state, and private document links. Read-only.",
"inputSchema": {
"properties": {
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"clientName": {
"description": "Optional: exact client name (case-insensitive).",
"type": "string"
},
"limit": {
"description": "Max invoices to return (default 25, max 100).",
"type": "number"
},
"status": {
"description": "Filter by invoice status (default active/outstanding).",
"enum": [
"active",
"open",
"overdue",
"needs_review",
"paid",
"void",
"superseded",
"all"
],
"type": "string"
}
},
"type": "object"
},
"name": "list_client_invoices",
"outputSchema": {
"additionalProperties": true,
"properties": {
"invoices": {
"items": {
"additionalProperties": true,
"properties": {
"advanceApplied": {
"type": "number"
},
"clientName": {
"type": [
"string",
"null"
]
},
"currency": {
"type": "string"
},
"displayStatus": {
"enum": [
"open",
"overdue",
"paid",
"void",
"superseded",
"needs_review"
],
"type": "string"
},
"grandTotal": {
"type": "number"
},
"invoiceId": {
"type": "string"
},
"invoiceNumber": {
"type": [
"string",
"null"
]
},
"issueDate": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"total": {
"type": "number"
}
},
"type": "object"
},
"type": "array"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"invoices"
],
"type": "object"
}
},
{
"description": "List ExpenseBot's fixed income tax categories. Unlike Expense Accounts, these are not user-configured. Use this BEFORE calling add_income so you pass an exact canonical category instead of guessing.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "list_income_categories",
"outputSchema": {
"additionalProperties": true,
"properties": {
"categories": {
"items": {
"type": "string"
},
"type": "array"
},
"legacy": {
"description": "Deprecated income categories still accepted on read.",
"items": {
"type": "string"
},
"type": "array"
},
"notes": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"categories"
],
"type": "object"
}
},
{
"description": "List the user's expense reports with pagination. Filter by status (All, Draft, Submitted, Shared). Each exact report includes its app, Bill Client, and accounting handoff URLs. Use the matching accounting URL only when the user explicitly asks to send or export that report; the app keeps organization, mapping, preview, and final confirmation.",
"inputSchema": {
"properties": {
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"filter": {
"default": "All",
"description": "Optional report-status filter; All returns every authorized status.",
"enum": [
"All",
"Draft",
"Submitted",
"Shared"
],
"type": "string"
},
"limit": {
"default": 20,
"description": "Maximum reports to return on this page.",
"type": "integer"
},
"page": {
"default": 1,
"description": "One-based results page to return.",
"type": "integer"
}
},
"type": "object"
},
"name": "list_reports",
"outputSchema": {
"additionalProperties": true,
"properties": {
"creationStatus": {
"description": "Provenance of the listing, e.g. \"existing_only\".",
"type": "string"
},
"filter": {
"type": "string"
},
"limit": {
"type": "integer"
},
"page": {
"type": "integer"
},
"reports": {
"description": "Present when the user has reports.",
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"responseGuidance": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"creationStatus"
],
"type": "object"
}
},
{
"description": "List the user's configured groups. ExpenseBot stores clients, projects, project codes, properties, and trips as tags (for example 'Client: Acme', 'Vegas Trip', or 'Property: 123 Main'). Use this when the user asks 'what groups/projects/clients do I have?' and before filtering, grouping, or reporting when the intended existing name is unclear.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "list_tags",
"outputSchema": {
"additionalProperties": true,
"properties": {
"success": {
"type": "boolean"
},
"tags": {
"description": "Client / project / trip groups (Column K values).",
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
}
},
"required": [
"success",
"tags"
],
"type": "object"
}
},
{
"description": "Mark an issued client invoice as paid after explicit confirmation. Requires a strict calendar-valid paidDate in YYYY-MM-DD format. This updates the accounts-receivable invoice record only; it never creates or moves an Income row. Record the actual payment separately or link an existing Income row.",
"inputSchema": {
"properties": {
"invoiceId": {
"description": "Stable invoice ID from list_client_invoices.",
"type": "string"
},
"paidAmount": {
"description": "Optional amount received.",
"minimum": 0,
"type": "number"
},
"paidDate": {
"description": "Calendar-valid payment date in YYYY-MM-DD format.",
"format": "date",
"type": "string"
},
"paidVia": {
"description": "Optional payment method or source, such as bank transfer or check.",
"type": "string"
}
},
"required": [
"invoiceId",
"paidDate"
],
"type": "object"
},
"name": "mark_client_invoice_paid",
"outputSchema": {
"additionalProperties": true,
"properties": {
"incomeRowCreated": {
"const": false,
"type": "boolean"
},
"invoiceId": {
"type": "string"
},
"invoiceNumber": {
"type": [
"string",
"null"
]
},
"message": {
"type": "string"
},
"paidAmount": {
"type": [
"number",
"null"
]
},
"paidDate": {
"format": "date",
"type": "string"
},
"paidVia": {
"type": "string"
},
"status": {
"const": "paid",
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"invoiceId",
"status",
"paidDate",
"paidAmount",
"paidVia",
"incomeRowCreated",
"message"
],
"type": "object"
}
},
{
"description": "Parse a natural language expense description into structured fields. Does NOT add the expense — just returns the parsed fields for review. Example: \"Lunch at Chipotle $15.50 today\" → {merchant: \"Chipotle\", total: 15.50, ...}",
"inputSchema": {
"properties": {
"text": {
"description": "Natural language expense (e.g., \"Coffee at Starbucks $6.50 yesterday\")",
"type": "string"
}
},
"required": [
"text"
],
"type": "object"
},
"name": "parse_expense",
"outputSchema": {
"additionalProperties": true,
"properties": {
"categoryNames": {
"items": {
"type": "string"
},
"type": "array"
},
"expenses": {
"items": {
"additionalProperties": true,
"properties": {
"amount": {
"type": "number"
},
"category": {
"type": "string"
},
"currency": {
"type": "string"
},
"date": {
"format": "date",
"type": "string"
},
"merchant": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"success": {
"type": "boolean"
},
"validationErrors": {
"items": {},
"type": "array"
}
},
"required": [
"success",
"expenses",
"validationErrors",
"categoryNames"
],
"type": "object"
}
},
{
"description": "Prepare a client invoice from a saved ExpenseBot report without creating it. Reads canonical report rows, allocates a unique invoice number, verifies the client ledger identity and advance, and returns exact totals plus a short-lived preparationId. Call this first, show the preview to the user, then call create_client_invoice after explicit confirmation. It reserves the invoice number and preview for 30 minutes but creates no invoice, document, Income row, email, or payment.",
"inputSchema": {
"properties": {
"advanceApplied": {
"description": "Verified client advance to apply against this invoice.",
"minimum": 0,
"type": "number"
},
"billToEmail": {
"description": "Optional client email printed on the invoice. This does not send email.",
"type": "string"
},
"clientAddress": {
"description": "Optional billing address printed in the invoice's Bill To section.",
"type": "string"
},
"clientName": {
"description": "Full client or business name shown on the invoice.",
"type": "string"
},
"customLineItems": {
"description": "Optional fee/service lines. They are taxed but never marked up.",
"items": {
"properties": {
"amount": {
"description": "Positive amount for this fee or service line in the invoice currency.",
"exclusiveMinimum": 0,
"type": "number"
},
"description": {
"description": "User-approved fee or service description shown on the invoice.",
"type": "string"
}
},
"required": [
"description",
"amount"
],
"type": "object"
},
"maxItems": 25,
"type": "array"
},
"invoiceNumber": {
"description": "Optional custom invoice number. ExpenseBot allocates one when omitted.",
"type": "string"
},
"issueDate": {
"description": "Calendar-valid invoice date in YYYY-MM-DD format.",
"format": "date",
"type": "string"
},
"markupPct": {
"description": "Markup applied only to rebilled expense rows.",
"maximum": 100,
"minimum": 0,
"type": "number"
},
"mentionSupportingReceipts": {
"description": "When true, note on the invoice that supporting receipts are available.",
"type": "boolean"
},
"paymentTerms": {
"description": "Invoice due terms. Defaults to the client's billing profile or net30.",
"enum": [
"due_on_receipt",
"net7",
"net14",
"net15",
"net30",
"net45",
"net60"
],
"type": "string"
},
"receiptIds": {
"description": "Optional exact report receipt IDs. Defaults to every current report expense.",
"items": {
"type": "string"
},
"type": "array"
},
"reportId": {
"description": "Saved ExpenseBot report ID to invoice.",
"type": "string"
},
"taxLabel": {
"description": "Tax label such as HST, GST, or VAT.",
"type": "string"
},
"taxPct": {
"description": "Tax percentage applied after markup and custom line items; use 0 for no tax.",
"maximum": 100,
"minimum": 0,
"type": "number"
}
},
"required": [
"reportId",
"clientName",
"issueDate"
],
"type": "object"
},
"name": "prepare_client_invoice",
"outputSchema": {
"additionalProperties": true,
"properties": {
"expiresAt": {
"format": "date-time",
"type": "string"
},
"preparationId": {
"type": "string"
},
"preview": {
"additionalProperties": true,
"properties": {
"advanceApplied": {
"type": "number"
},
"balanceDue": {
"type": "number"
},
"clientName": {
"type": "string"
},
"clientTag": {
"type": "string"
},
"currency": {
"type": "string"
},
"customLineCount": {
"minimum": 0,
"type": "integer"
},
"dueDate": {
"format": "date",
"type": "string"
},
"expenseCount": {
"minimum": 0,
"type": "integer"
},
"grandTotal": {
"type": "number"
},
"hasTaxRegistration": {
"type": "boolean"
},
"invoiceNumber": {
"type": "string"
},
"issueDate": {
"format": "date",
"type": "string"
},
"markupAmount": {
"type": "number"
},
"markupPct": {
"type": "number"
},
"paymentTerms": {
"type": "string"
},
"reportId": {
"type": "string"
},
"reportTitle": {
"type": "string"
},
"subtotal": {
"type": "number"
},
"taxAmount": {
"type": "number"
},
"taxConfirmationRequired": {
"type": "boolean"
},
"taxLabel": {
"type": "string"
},
"taxPct": {
"type": "number"
}
},
"type": "object"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"preparationId",
"expiresAt",
"preview"
],
"type": "object"
}
},
{
"description": "Process specific Gmail emails as receipts. Pass Gmail message IDs and they'll be converted to PDF, extracted by AI, and added to the user's expense spreadsheet. Max 25 emails per request. Requires Gmail to be connected in ExpenseBot settings.",
"inputSchema": {
"properties": {
"accountEmail": {
"description": "Optional: which Gmail account to use (for users with multiple linked accounts)",
"type": "string"
},
"emailIds": {
"description": "Gmail message IDs to process as receipts",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"emailIds"
],
"type": "object"
},
"name": "process_gmail_receipts",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
},
"emailCount": {
"minimum": 0,
"type": "integer"
},
"error": {
"type": "string"
},
"message": {
"type": "string"
},
"processInfo": {
"additionalProperties": true,
"properties": {
"processingCompleted": {
"type": "boolean"
},
"sessionId": {
"type": "string"
},
"status": {
"type": "string"
}
},
"type": "object"
},
"processedItems": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"reviewExpensesUrl": {
"type": "string"
},
"spreadsheetUrl": {
"type": [
"string",
"null"
]
},
"submissionId": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"message"
],
"type": "object"
}
},
{
"description": "Request support for an accounting package only after checking ExpenseBot's reviewed direct and import-file destinations and finding no matching package. Do not use this for a listed or Beta destination, a general compatibility question, or without the exact package name. This is a confirmation-gated write: after the user approves, it creates one deduplicated request bound to the authenticated ExpenseBot account and emails ExpenseBot's internal team. It does not create an integration or make the requested package immediately available. On accepted or previously recorded requests, tell the user ExpenseBot will email them within one week with an update.",
"inputSchema": {
"properties": {
"packageName": {
"description": "Exact accounting package requested by the user.",
"type": "string"
}
},
"required": [
"packageName"
],
"type": "object"
},
"name": "request_accounting_integration",
"outputSchema": {
"additionalProperties": true,
"properties": {
"duplicate": {
"type": "boolean"
},
"message": {
"type": "string"
},
"packageName": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"duplicate",
"packageName",
"message"
],
"type": "object"
}
},
{
"description": "Trigger a background Gmail scan to discover and process receipt emails from the last ~60 days (default). Runs asynchronously — returns immediately, user gets an email summary when done. Like clicking \"Find Receipts in Gmail\" in the UI. For whole PAST YEARS (e.g. 2023, or 2020-2022) use scan_gmail_years instead; to check a scan's progress use get_scan_status.",
"inputSchema": {
"properties": {
"accountEmail": {
"description": "Optional: which Gmail account to scan",
"type": "string"
},
"endDate": {
"description": "Optional: explicit end date (YYYY-MM-DD)",
"format": "date",
"type": "string"
},
"lookbackDays": {
"default": 60,
"description": "How many days back to scan (default 60, max depends on subscription)",
"type": "integer"
},
"startDate": {
"description": "Optional: explicit start date (YYYY-MM-DD) instead of lookbackDays",
"format": "date",
"type": "string"
}
},
"type": "object"
},
"name": "scan_gmail",
"outputSchema": {
"additionalProperties": true,
"properties": {
"_metadata": {
"additionalProperties": true,
"type": "object"
},
"data": {
"additionalProperties": true,
"type": "object"
},
"message": {
"type": "string"
},
"orchestrated": {
"type": "boolean"
},
"reviewExpensesUrl": {
"type": [
"string",
"null"
]
},
"scanInProgress": {
"type": "boolean"
},
"sessionId": {
"type": "string"
},
"spreadsheetUrl": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"submissionId": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Scan one or more COMPLETED PRIOR years of the user's Gmail for receipts (e.g. years:[2023] or years:[2020,2021,2022]). Long-running background job: the first eligible year starts immediately and the rest queue, running one at a time (each full year typically takes a couple of hours; the user can close the app). ALWAYS call first WITHOUT confirmStart to preview which years are eligible, then ask the user to confirm, then call again with confirmStart:true. For receipts from the last ~60 days use scan_gmail instead. To check how a scan is going, use get_scan_status. The current in-progress year cannot be year-scanned.",
"inputSchema": {
"properties": {
"accountEmail": {
"description": "Optional: which connected Gmail inbox to scan (multi-inbox users). Defaults to the primary.",
"type": "string"
},
"confirmStart": {
"description": "Omit or false = preview only (no scan starts). Must be exactly true to start scanning.",
"type": "boolean"
},
"years": {
"description": "Completed prior years to scan, e.g. [2020, 2021]. Max 10.",
"items": {
"type": "number"
},
"type": "array"
}
},
"required": [
"years"
],
"type": "object"
},
"name": "scan_gmail_years",
"outputSchema": {
"additionalProperties": true,
"allOf": [
{
"if": {
"properties": {
"preview": {
"const": true
}
},
"required": [
"preview"
]
},
"then": {
"required": [
"account",
"eligible",
"alreadyScanned",
"notEligible"
]
}
}
],
"properties": {
"account": {
"type": "string"
},
"alreadyQueued": {
"items": {
"type": "integer"
},
"type": "array"
},
"alreadyScanned": {
"items": {
"type": "integer"
},
"type": "array"
},
"archiveThroughYear": {
"type": "integer"
},
"eligible": {
"items": {
"type": "integer"
},
"type": "array"
},
"gmailUrl": {
"type": "string"
},
"message": {
"type": "string"
},
"notEligible": {
"items": {
"additionalProperties": true,
"properties": {
"code": {
"type": "string"
},
"reason": {
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"year",
"reason"
],
"type": "object"
},
"type": "array"
},
"preview": {
"type": "boolean"
},
"queuedYears": {
"items": {
"type": "integer"
},
"type": "array"
},
"scanning": {
"items": {
"type": "integer"
},
"type": "array"
},
"startedYear": {
"type": [
"integer",
"null"
]
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"message"
],
"type": "object"
}
},
{
"description": "Search the user's ExpenseBot data — expenses, reports, and knowledge base. Returns matching items with id, title, and url. Use the returned id with the `fetch` tool to get full details. Powers Deep Research and Company Knowledge integrations.",
"inputSchema": {
"properties": {
"query": {
"description": "Natural language search (e.g., 'Uber rides in March', 'Q1 client report', 'mileage Acme')",
"type": "string"
}
},
"required": [
"query"
],
"type": "object"
},
"name": "search",
"outputSchema": {
"properties": {
"results": {
"items": {
"properties": {
"id": {
"description": "Pass to `fetch` to retrieve full content",
"type": "string"
},
"title": {
"type": "string"
},
"url": {
"format": "uri",
"type": "string"
}
},
"required": [
"id",
"title"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"results"
],
"type": "object"
}
},
{
"description": "Search and filter the user's expenses. Returns matching expense rows from their spreadsheet. Filter by category, merchant, date range, amount, or tags. Results are paginated: when hasMore is true, call again with nextCursor and the same filters. Do not split a date range into repeated overlapping searches.\n\nUse the optional query parameter for deterministic natural-language recall over merchant, city/location, Notes (including receipt items, delivery source, payer, and Business purpose), Tag, and category. Each matching result includes matchedFields and a short matchReason so you can explain why it was selected. When several rows plausibly match, a disambiguation list is returned; each option carries the exact expenseId. Structured filters (categories, merchants, dateRange, tags, minAmount, maxAmount) combine with the query using AND semantics. Each result includes expenseId, the exact durable Receipt ID required by update_expense.",
"inputSchema": {
"properties": {
"categories": {
"description": "Filter by expense categories (e.g., ['Travel', 'Meals'])",
"items": {
"type": "string"
},
"type": "array"
},
"clientEmail": {
"description": "Client account email. Accountants may use this only for an accepted ExpenseBot client.",
"format": "email",
"type": "string"
},
"cursor": {
"description": "Opaque nextCursor returned by the previous search_expenses page. Reuse the same filters; never construct or edit this value.",
"type": "string"
},
"dateRange": {
"description": "Time period filter. Use exactly one variant — pick the shape that matches the user's phrasing.",
"oneOf": [
{
"additionalProperties": false,
"properties": {
"month": {
"description": "Month name or 1-12 (e.g., 'March' or '3')",
"type": "string"
},
"type": {
"const": "month",
"type": "string"
},
"year": {
"description": "4-digit year (e.g., 2026)",
"type": "integer"
}
},
"required": [
"type",
"month",
"year"
],
"title": "Specific month",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"quarter": {
"maximum": 4,
"minimum": 1,
"type": "integer"
},
"type": {
"const": "quarter",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"quarter",
"year"
],
"title": "Quarter of a year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "year",
"type": "string"
},
"year": {
"type": "integer"
}
},
"required": [
"type",
"year"
],
"title": "Full year",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"end": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"start": {
"description": "ISO date YYYY-MM-DD (inclusive)",
"format": "date",
"type": "string"
},
"type": {
"const": "specific",
"type": "string"
}
},
"required": [
"type",
"start",
"end"
],
"title": "Specific date range",
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"type": {
"const": "relative",
"type": "string"
},
"value": {
"description": "Relative range phrase, e.g., 'last month', 'YTD', 'this quarter', 'last 7 days'",
"type": "string"
}
},
"required": [
"type",
"value"
],
"title": "Relative natural-language range",
"type": "object"
}
]
},
"hasReceipt": {
"description": "When true, return only expenses with a receipt link. When false, return only expenses without one.",
"type": "boolean"
},
"limit": {
"default": 20,
"description": "Results per page (default 20, maximum 50)",
"maximum": 50,
"minimum": 1,
"type": "integer"
},
"maxAmount": {
"description": "Maximum expense amount",
"type": "number"
},
"merchants": {
"description": "Filter by merchant names (e.g., ['Uber', 'Starbucks'])",
"items": {
"type": "string"
},
"type": "array"
},
"minAmount": {
"description": "Minimum expense amount",
"type": "number"
},
"query": {
"description": "Optional natural-language recall over the user's existing expense fields (merchant, city/location, Notes including receipt items, delivery source, payer, and Business purpose, Tag, category). Deterministic case-insensitive matching — no embeddings or model classifiers. Combine with structured filters using AND semantics. Examples: 'Tribeca restaurant', 'Bodewell project hardware store', 'client dinner note'.",
"type": "string"
},
"tags": {
"description": "Filter by tags",
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
},
"name": "search_expenses",
"outputSchema": {
"additionalProperties": true,
"properties": {
"count": {
"description": "Rows matched by the filters.",
"type": "integer"
},
"expenses": {
"description": "Matching expense rows, newest first, paginated.",
"items": {
"additionalProperties": true,
"properties": {
"amount": {
"description": "Functional/home-currency amount.",
"type": "number"
},
"category": {
"type": "string"
},
"currency": {
"description": "Home currency code when available.",
"type": "string"
},
"date": {
"type": "string"
},
"dateIso": {
"description": "Canonical YYYY-MM-DD when available.",
"type": "string"
},
"expenseId": {
"description": "Durable Receipt ID (Column Q). The only identity accepted by update_expense.",
"type": "string"
},
"location": {
"type": "string"
},
"matchReason": {
"type": "string"
},
"matchedFields": {
"items": {
"type": "string"
},
"type": "array"
},
"merchant": {
"type": "string"
},
"originalAmount": {
"type": "number"
},
"originalCurrency": {
"type": "string"
},
"tag": {
"description": "Column K group (client/project/trip).",
"type": "string"
},
"updateEligible": {
"type": "boolean"
}
},
"required": [
"expenseId",
"date",
"amount",
"tag",
"updateEligible"
],
"type": "object"
},
"type": "array"
},
"hasMore": {
"type": "boolean"
},
"message": {
"type": "string"
},
"nextCursor": {
"description": "Opaque signed continuation; reuse with identical filters.",
"type": [
"string",
"null"
]
},
"pageInfo": {
"additionalProperties": true,
"type": "object"
},
"queryApplied": {
"type": "string"
},
"total": {
"description": "Sum of the matched set in home currency.",
"type": "number"
},
"totalMatched": {
"description": "Present for free-text searches.",
"type": "integer"
}
},
"required": [
"expenses",
"count",
"total"
],
"type": "object"
}
},
{
"description": "Search ExpenseBot's complete product and help knowledge base. Use this before guessing when the user asks how a feature works, what ExpenseBot supports, or needs setup, billing, Gmail, scan, report, workflow, or troubleshooting instructions. Set fullAnswer:true on the first call when the user asks for complete, detailed, or step-by-step instructions. Use the default excerpt mode for discovery and quick answers; if an excerpt is truncated, call again with fullAnswer:true before presenting complete instructions. Each result has a stable id.",
"inputSchema": {
"properties": {
"fullAnswer": {
"default": false,
"description": "Set true on the first call for complete, detailed, or step-by-step instructions. Leave false for discovery or quick-answer excerpts.",
"type": "boolean"
},
"limit": {
"default": 5,
"description": "Number of ranked matching articles to return (1-10).",
"maximum": 10,
"minimum": 1,
"type": "integer"
},
"query": {
"description": "Search terms",
"type": "string"
}
},
"required": [
"query"
],
"type": "object"
},
"name": "search_knowledge",
"outputSchema": {
"additionalProperties": true,
"properties": {
"results": {
"items": {
"additionalProperties": true,
"properties": {
"answer": {
"type": "string"
},
"id": {
"type": "string"
},
"question": {
"type": "string"
},
"relevance": {
"type": "number"
},
"truncated": {
"type": "boolean"
},
"type": {
"type": "string"
}
},
"required": [
"id",
"question",
"answer"
],
"type": "object"
},
"type": "array"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"results"
],
"type": "object"
}
},
{
"description": "Review and then post an ExpenseBot report to the owner's accounting destination. STEP 1: call with provider, reportId, and optional mode/mappings, without confirm. The server reads the live report and destination, applies Omit/Personal/split/date/currency rules, checks Zoho bank-feed matches, and returns the complete proposal plus proposalId. NOTHING is posted in step 1. Show the complete proposal and ask for approval. STEP 2: call with only provider, proposalId, and confirm:true. The server posts only the frozen, account-bound proposal, revalidates live state, and rejects changed reports or mappings. Never add mapping fields to the confirmation call. Owner accounts only; acting for a client is not supported. Agent-driven posting is currently available only for Zoho Books (zoho_books); QuickBooks Online, Xero, Wave, and FreeAgent are intentionally not accepted by this tool yet because their previews cannot freeze/revalidate the canonical provider plan that shareReport executes. Use those providers' web flows until they satisfy the full push contract.",
"inputSchema": {
"properties": {
"accountMappings": {
"additionalProperties": {
"type": "string"
},
"description": "Step 1 only: ExpenseBot category to live destination account ID overrides.",
"type": "object"
},
"confirm": {
"description": "Step 2 only: true posts the exact staged proposal. No other options may change.",
"type": "boolean"
},
"forceRepush": {
"description": "Step 1 only: explicitly include disclosed uncertain prior outcomes in the frozen proposal.",
"type": "boolean"
},
"mode": {
"description": "Step 1 only (Zoho Books): automatic document treatment, paid expenses, or bills.",
"enum": [
"auto",
"expense",
"bill"
],
"type": "string"
},
"proposalId": {
"description": "Step 2 only: proposalId returned by the read-only preview.",
"type": "string"
},
"provider": {
"description": "Accounting destination provider. Agent-driven posting is currently available only for Zoho Books.",
"enum": [
"zoho_books"
],
"type": "string"
},
"reportId": {
"description": "Step 1 only: ExpenseBot report ID owned by the signed-in account.",
"type": "string"
},
"reportingTagMappings": {
"additionalProperties": {
"properties": {
"optionId": {
"description": "Live option ID belonging to tagId, returned in the step-1 proposal.",
"type": "string"
},
"tagId": {
"description": "Live accounting-provider reporting-tag ID returned in the step-1 proposal.",
"type": "string"
}
},
"required": [
"tagId",
"optionId"
],
"type": "object"
},
"description": "Step 1 only: ExpenseBot tag to live destination reporting-tag option.",
"type": "object"
},
"taxMappings": {
"additionalProperties": {
"type": "string"
},
"description": "Step 1 only: observed tax-rate string to live destination tax ID overrides.",
"type": "object"
}
},
"required": [
"provider"
],
"type": "object"
},
"name": "send_report_to_accounting",
"outputSchema": {
"additionalProperties": true,
"allOf": [
{
"if": {
"properties": {
"phase": {
"const": "preview"
}
},
"required": [
"phase"
]
},
"then": {
"required": [
"expiresAt",
"confirmationRequired",
"confirmationQuestion",
"confirmationContract"
]
}
},
{
"if": {
"properties": {
"phase": {
"const": "completed"
}
},
"required": [
"phase"
]
},
"then": {
"required": [
"reportId",
"finalAnswer",
"responseGuidance"
]
}
}
],
"properties": {
"confirmationContract": {
"type": "string"
},
"confirmationQuestion": {
"type": "string"
},
"confirmationRequired": {
"type": "boolean"
},
"counts": {
"additionalProperties": true,
"type": "object"
},
"created": {
"minimum": 0,
"type": "integer"
},
"expiresAt": {
"format": "date-time",
"type": "string"
},
"finalAnswer": {
"type": "string"
},
"idempotentReplay": {
"type": "boolean"
},
"matchedExisting": {
"minimum": 0,
"type": "integer"
},
"omittedExpenses": {
"minimum": 0,
"type": "integer"
},
"organization": {
"additionalProperties": true,
"type": "object"
},
"phase": {
"enum": [
"preview",
"completed"
],
"type": "string"
},
"proposalId": {
"type": "string"
},
"provider": {
"type": "string"
},
"reconciliationRequired": {
"type": "boolean"
},
"reportId": {
"type": "string"
},
"responseGuidance": {
"type": "string"
},
"skippedAlreadyPushed": {
"minimum": 0,
"type": "integer"
},
"success": {
"type": "boolean"
},
"units": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
}
},
"required": [
"success",
"phase",
"provider",
"proposalId"
],
"type": "object"
}
},
{
"description": "Share one existing expense report after confirmation. Default to reviewer: they can open and edit that report's Google Sheet, inspect receipts, comment, approve, or request changes, but cannot act for the owner or use accounting integrations. Use accountant only when the user explicitly asks for ongoing accounting access; it creates the established accountant relationship and broader report-management workflow.",
"inputSchema": {
"properties": {
"recipientRole": {
"default": "reviewer",
"description": "Use reviewer for report-only approval. Use accountant only for explicit ongoing accountant access.",
"enum": [
"reviewer",
"accountant"
],
"type": "string"
},
"recipients": {
"items": {
"format": "email",
"type": "string"
},
"type": "array"
},
"reportId": {
"type": "string"
}
},
"required": [
"reportId",
"recipients"
],
"type": "object"
},
"name": "share_report",
"outputSchema": {
"additionalProperties": true,
"properties": {
"finalAnswer": {
"type": "string"
},
"message": {
"type": "string"
},
"recipientRole": {
"type": "string"
},
"responseGuidance": {
"type": "string"
},
"sharedWith": {
"items": {
"format": "email",
"type": "string"
},
"type": "array"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"message",
"sharedWith",
"recipientRole"
],
"type": "object"
}
},
{
"description": "Submit a photo or PDF of a receipt for processing. Covers requests phrased as 'log this', 'log this receipt', 'save this receipt', 'expense this', or 'add this to my expenses', including when the user simply shares a photo of a receipt or invoice. The receipt image is validated, uploaded to cloud storage, and processed by AI to extract vendor, amount, date, tax, and category. The expense appears in the user's spreadsheet in about 1-3 minutes, and longer for PDFs or large batches. Handles images and PDFs, mixed together in one batch.\n\nTO SEND FILES (preferred, and required for PDFs): call this tool with filesToUpload listing every file the user gave you. It returns one signed upload URL per file. Upload them ONE AT A TIME with an HTTP PUT, telling the user which file you just finished and how many remain, then call this tool ONCE with uploadRefs for all of them — that processes the whole set as a single batch, like the ExpenseBot web app. Do not call this tool once per file.\n\nUse the photo parameter for one image or PDF attached in ChatGPT. MCP clients that cannot supply file references may use photoBase64 for one small image; use the upload flow for large files or batches.\n\nOptional note and tag values use the same receipt metadata path as ExpenseBot's camera, file uploader, and forwarded-email intake. The note is stored in the Notes column (L); the tag is stored in the Tag column (K). Batch defaults apply to every file, and each uploadRefs item may override either value for that file.",
"inputSchema": {
"properties": {
"filename": {
"description": "Optional filename (e.g., 'lunch_receipt.jpg')",
"type": "string"
},
"filesToUpload": {
"description": "Request upload URLs for one or more files. Include EVERY file the user provided in a single call. Returns one signedUrl + uploadRef per file; upload each, then call this tool again with uploadRefs.",
"items": {
"properties": {
"filename": {
"description": "Original filename, e.g. 'lunch.jpg' or 'invoice.pdf'.",
"type": "string"
},
"id": {
"description": "Your own identifier for this file, echoed back so you can match URLs to files. E.g. '1', '2'.",
"type": "string"
},
"mimeType": {
"description": "MIME type. Must match the Content-Type header used in the PUT exactly. The real format is re-detected from the file's contents on submit, so a close guess still works.",
"enum": [
"image/jpeg",
"image/png",
"image/heic",
"image/heif",
"image/webp",
"application/pdf"
],
"type": "string"
}
},
"required": [
"filename"
],
"type": "object"
},
"type": "array"
},
"mimeType": {
"description": "MIME type of the file (default: image/jpeg)",
"enum": [
"image/jpeg",
"image/png",
"image/heic",
"image/webp",
"application/pdf"
],
"type": "string"
},
"note": {
"description": "Batch-level context note applied to every file in the upload. Individual files may override this with their own note. Stored through the existing receipt-note pipeline in the Notes column (L).",
"type": "string"
},
"photo": {
"description": "Single attached receipt image or PDF supplied by ChatGPT. For multiple files or large files, use filesToUpload and uploadRefs instead.",
"properties": {
"download_url": {
"description": "Temporary HTTPS URL supplied by ChatGPT for downloading the attachment.",
"format": "uri",
"type": "string"
},
"file_id": {
"description": "Attachment identifier supplied by ChatGPT.",
"type": "string"
},
"file_name": {
"description": "Original attachment filename supplied by ChatGPT.",
"type": "string"
},
"mime_type": {
"description": "Attachment MIME type supplied by ChatGPT.",
"type": "string"
}
},
"required": [
"download_url",
"file_id"
],
"type": "object"
},
"photoBase64": {
"description": "Legacy fallback for MCP clients that send one small image as complete base64 data. ChatGPT should use photo or uploadRefs instead.",
"type": "string"
},
"tag": {
"description": "Batch-level project or client tag applied to every expense row. Individual files may override it. Stored in the Tag column (K) through the same path as the web receipt uploader.",
"type": "string"
},
"uploadRef": {
"description": "Single-file shorthand for uploadRefs. Use uploadRefs when there is more than one file.",
"type": "string"
},
"uploadRefs": {
"description": "Submit previously uploaded files as ONE batch. Include every uploadRef from the filesToUpload step. If any file fails validation the whole batch is rejected and nothing is processed.",
"items": {
"properties": {
"id": {
"description": "The id you supplied in filesToUpload.",
"type": "string"
},
"note": {
"description": "Context note for this specific file. Overrides the batch-level note if both are provided. Uses the existing receipt-note pipeline and is stored in the Notes column (L).",
"type": "string"
},
"tag": {
"description": "Project or client tag for this specific file. Overrides the batch-level tag and is stored in the Tag column (K).",
"type": "string"
},
"uploadRef": {
"description": "uploadRef returned for that file.",
"type": "string"
}
},
"required": [
"uploadRef"
],
"type": "object"
},
"type": "array"
}
},
"required": [],
"type": "object"
},
"name": "submit_receipt",
"outputSchema": {
"additionalProperties": true,
"allOf": [
{
"if": {
"properties": {
"status": {
"const": "upload_required"
}
},
"required": [
"status"
]
},
"then": {
"required": [
"data"
]
}
}
],
"properties": {
"data": {
"additionalProperties": true,
"properties": {
"expiresInSeconds": {
"minimum": 1,
"type": "integer"
},
"files": {
"items": {
"additionalProperties": true,
"properties": {
"filename": {
"type": "string"
},
"id": {
"type": "string"
},
"mimeType": {
"type": "string"
},
"requiredHeaders": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"signedUrl": {
"type": "string"
},
"uploadRef": {
"type": "string"
}
},
"type": "object"
},
"type": "array"
},
"requiredHeaders": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"reviewExpensesUrl": {
"type": "string"
},
"signedUrl": {
"type": "string"
},
"spreadsheetUrl": {
"type": [
"string",
"null"
]
},
"submissionId": {
"type": "string"
},
"uploadRef": {
"type": "string"
}
},
"type": "object"
},
"fileCount": {
"minimum": 0,
"type": "integer"
},
"files": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"message": {
"type": "string"
},
"reviewExpensesUrl": {
"type": "string"
},
"spreadsheetUrl": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
"statusCode": {
"type": "integer"
},
"submissionId": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "Trace what happened to a specific receipt, invoice, forwarded email, Gmail receipt, or PDF in ExpenseBot processing. Use when the user asks where a document went, why it did not show up, whether it was skipped, routed to income, deduped, or removed as a duplicate. Returns a read-only timeline from the user's lineage audit trail.",
"inputSchema": {
"properties": {
"days": {
"default": 30,
"description": "How many days back to search.",
"maximum": 180,
"minimum": 1,
"type": "integer"
},
"limit": {
"default": 80,
"description": "Maximum lineage events to return.",
"maximum": 250,
"minimum": 1,
"type": "integer"
},
"messageId": {
"description": "Gmail message ID if known.",
"type": "string"
},
"outcome": {
"default": "all",
"description": "Optional processing outcome filter.",
"enum": [
"all",
"created",
"skipped",
"suggested",
"income_routed",
"deduped",
"removed_as_duplicate"
],
"type": "string"
},
"query": {
"description": "Merchant, sender, subject, invoice number, or phrase to search for, e.g. 'Blue Rocket', 'Driversnote', 'Uber receipt'.",
"type": "string"
},
"receiptId": {
"description": "ExpenseBot receipt/invoice ID if known, e.g. IR-123, RO-abc, RR-456.",
"type": "string"
},
"threadId": {
"description": "Gmail thread ID if known.",
"type": "string"
}
},
"type": "object"
},
"name": "trace_document",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"properties": {
"events": {
"items": {
"additionalProperties": true,
"type": "object"
},
"type": "array"
},
"filters": {
"additionalProperties": true,
"type": "object"
},
"searchLabel": {
"type": "string"
},
"summary": {
"additionalProperties": true,
"type": "object"
}
},
"type": "object"
},
"message": {
"type": "string"
},
"success": {
"type": "boolean"
},
"type": {
"const": "lineage_trace",
"type": "string"
}
},
"required": [
"success",
"type",
"message",
"data"
],
"type": "object"
}
},
{
"description": "Update a single existing expense row's category, tag, merchant, or business purpose (e.g. 'recategorize expense X to Meals' or 'tag it Client – Acme'). Identify it only by the exact expenseId returned by search_expenses (the full Receipt ID from Column Q). Never use a date, displayed number, or sheet row number as expenseId. Two-phase operation: calls with confirm omitted or false return a preview of the current→proposed change and make no change. After user confirmation, a call with confirm:true applies the proposed change. If the expense is not found, search once for a current exact expenseId; never retry the same stale ID. If a business-purpose update exceeds the Notes capacity, stop and send the user to https://www.expensebot.ai/review-expenses?source=mcp instead of retrying. Only category/tag/merchant/businessPurpose are editable — amounts, dates, and notes are not editable via the assistant. Does not create or delete rows.",
"inputSchema": {
"properties": {
"confirm": {
"description": "Omit or false = preview only (no write). Must be exactly true to apply the change.",
"type": "boolean"
},
"expenseId": {
"description": "Exact full Receipt ID from Column Q, returned as expenseId by search_expenses.",
"type": "string"
},
"fields": {
"additionalProperties": false,
"description": "Partial update object. Include one or more of these optional keys; omit every field that should remain unchanged.",
"maxProperties": 4,
"minProperties": 1,
"properties": {
"businessPurpose": {
"type": "string"
},
"category": {
"type": "string"
},
"merchant": {
"type": "string"
},
"tag": {
"type": "string"
}
},
"type": "object"
}
},
"required": [
"expenseId",
"fields"
],
"type": "object"
},
"name": "update_expense",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"properties": {
"applied": {
"type": "boolean"
},
"diff": {
"items": {
"additionalProperties": true,
"properties": {
"column": {
"type": "string"
},
"columnLetter": {
"type": "string"
},
"field": {
"type": "string"
},
"from": {},
"to": {}
},
"required": [
"field",
"from",
"to"
],
"type": "object"
},
"type": "array"
},
"dryRun": {
"type": "boolean"
},
"expenseId": {
"type": "string"
},
"preview": {
"type": "boolean"
},
"reviewExpenseUrl": {
"type": "string"
},
"unmapped": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"applied",
"expenseId",
"diff",
"unmapped"
],
"type": "object"
},
"finalAnswer": {
"type": "string"
},
"message": {
"type": "string"
},
"responseGuidance": {
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success",
"message",
"data"
],
"type": "object"
}
},
{
"description": "Can I afford $X/month? Recomputes the user's Safe Monthly Draw (how much they can safely pay themselves) with an added recurring monthly cost, and returns a yes/tight/no verdict plus the before/after numbers. Use for questions like 'can I afford a $500/mo hire' or 'what if I add a $200/mo software subscription'. Requires at least 3 months of income history — otherwise returns insufficient_data rather than a guess.",
"inputSchema": {
"properties": {
"deltaMonthly": {
"description": "The new recurring monthly cost being considered, in the user's home currency",
"minimum": 0,
"type": "number"
}
},
"required": [
"deltaMonthly"
],
"type": "object"
},
"name": "whatif_afford",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"description": "The full what-if scenario: assumptions, evidence, and computed outcome.",
"type": "object"
},
"message": {
"description": "Scenario narrative; may be absent if the scenario carried no template or evidence.",
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "What if a client pays late or leaves? mode='late30' shifts that client's OPEN invoice amounts out of the near-term expectation (they still owe it, it's just not landing this month). mode='gone' removes that client's trailing monthly income contribution and recomputes Safe Draw against the reduced baseline. Use for questions like 'what if Acme Corp pays 30 days late' or 'what happens if I lose my biggest client'. Client identity is matched against the Income tab's tag/source/description fields — best effort, not a guaranteed match.",
"inputSchema": {
"properties": {
"clientName": {
"description": "The client's name as it appears on invoices or income rows",
"type": "string"
},
"mode": {
"description": "'late30' = shift open invoices 30 days late; 'gone' = client stops paying entirely",
"enum": [
"late30",
"gone"
],
"type": "string"
}
},
"required": [
"clientName",
"mode"
],
"type": "object"
},
"name": "whatif_client",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"description": "The full what-if scenario: assumptions, evidence, and computed outcome.",
"type": "object"
},
"message": {
"description": "Scenario narrative; may be absent if the scenario carried no template or evidence.",
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
},
{
"description": "What should I set aside for taxes? Surfaces the same monthly tax set-aside estimate already computed for Safe Monthly Draw — wiring, not new math. Flat-rate estimate (default 30%) against trailing income minus recurring + variable spend. Use for 'how much should I set aside for taxes this month'.",
"inputSchema": {
"properties": {},
"type": "object"
},
"name": "whatif_tax_setaside",
"outputSchema": {
"additionalProperties": true,
"properties": {
"data": {
"additionalProperties": true,
"description": "The full what-if scenario: assumptions, evidence, and computed outcome.",
"type": "object"
},
"message": {
"description": "Scenario narrative; may be absent if the scenario carried no template or evidence.",
"type": "string"
},
"success": {
"type": "boolean"
}
},
"required": [
"success"
],
"type": "object"
}
}
]
}Verify it yourself
curl -s https://api.teppi.xyz/v1/evidence/sha256:704c1d1ce2420b0fb0184f08d669c2d7f8b4f45a92d1e90bb2cbf70c3265f5dd | sha256sum