Skip to main content
Glama

Set the category of transactions

well_set_transaction_category

Set the category of one or more transactions to ONE label — the write that clears a categorization gate.

REQUIRED: transaction_ids, the ids of the rows from a read (well_list_uncategorized_window, a transaction list, a query). category — the LABEL, exactly as that read returned it on the row's suggestion, or another label from the closed list this schema carries. The vocabulary is fixed: there is no free-text category and no way to mint one.

A yes, "valide la proposition" or "ok" after another message since the list or proposal it answers names no row. Do not call this tool for that reply. Read the remaining rows again with the uncategorized-window read so Well shows a fresh list; a pick on that list then saves.

decision records HOW the category was chosen, and it changes what the row keeps.

  • accepted_classifier_suggestion — the user affirmed the label the classifier had already put on the row. The row keeps category_source: "classifier" and its confidence score, and the affirmation is stamped as category_confirmed_at. Send this ONLY when the label equals the classifier's own stored suggestion.

  • user_choice — the user picked the label themselves. The row records category_source: "user" with no score.

The server verifies an accepted_classifier_suggestion claim against the row it is writing and downgrades it to user_choice when the stored suggestion is not that label, so the claim can never manufacture classifier provenance. Omitting decision is a user_choice.

A row from well_list_uncategorized_window never qualifies for the affirmation. That read returns rows carrying NO category at all, so there is no stored classifier value to affirm and the claim would be downgraded every time. Its categorySuggestions are PENDING proposals, not a stored category. Clearing that gate is always a user_choice; the affirmation exists for a surface that lists rows the classifier already categorized.

Categorizing a row does NOT move it in or out of the internal-transfer rule — that rule counts payment-means legs and no label affects it. What a category DOES change is exemption matching: an uncategorized row can never be matched by an exemption and always stays in a sum.

Send category: null to set the row back to no category. Use it when the user asks to remove or empty a transaction's category. The row then has no category, and the user owns that state: neither the classifier nor a counterparty rule fills it again. A null category takes no decision. "Uncategorised / suspense" is NOT "no category": it is a real category that books the line to the suspense account. Never send it to remove a category.

The server refuses a null category with the typed reason TRANSACTION_CATEGORY_BOOKED_IN_LEDGER when the row's category is already booked in the ledger. Tell the user the category is booked in the ledger, and offer to change it to another category instead.

One call for every row that takes the same label. When the person names many rows for one category ("put the 12 Uber rides under Transport"), read the rows, then send ALL their ids in transaction_ids on ONE call: never one call per row. Up to 50 ids per call; for more rows, split them into calls of at most 50. Rows that take different labels go in separate calls, one per label. decision applies to every row of the call, and the server checks an affirmation row by row.

Each row is saved on its own. The answer gives written and failed and one entry per row in results. When some rows failed, say which rows were not changed: never report them as done. totals sums the saved rows, one amount per currency, signed as the bank books them: say each amount without its sign, a whole amount with no decimals (€412, never €412.00), and never add up amounts yourself.

When every row saved, answer with ONE read-back line: "Saved: the <the rows, in the person's words>, in total, are under <the category, in the person's words>." For example "Saved: the 12 Uber rides, €412 in total, are under Transport." For one row, name it: "Saved: transaction Figma, €45, 12 Oct: category Software." Add nothing else: no list of the rows, no question.

The list depends on the workspace. A personal space files household categories, a company workspace business categories, and both file the shared ones. A label outside the workspace's list writes nothing and returns refusal_reason with allowed_categories: send a label from that list.

A name that fits more than one label is a question, never a pick. When the words the person used fit two or more labels of the list (for example "Logiciels" or "software" fits both "Developer & engineering software" and "Business & productivity software"), do not call this tool: ask which one, naming each label that fits in the person's words, and call it once they answer. Never choose the nearer label, and never put two labels on one call.

confirm_first: true when the person's message asks that nothing change before they approve (for example "ne modifie rien avant mon accord", "show me first", "wait for my OK"). Omit it otherwise. In Well's own sessions the call then waits on Well's confirmation card, even when the person lets Well act alone for this kind of change. In another assistant connected to Well with the person's own sign-in, Well shows no card and a flagged call changes nothing: it returns awaiting_confirmation: true. Show the person the transaction and the label, ask for their OK in the conversation, then call again without the flag. Without the flag, your host's own approval of the call is the confirmation. Well checks that the person is signed in and a member of the workspace, and that the label is a category of this workspace. A connection made with an API key cannot change a category.

This write changes Well's copy of the transaction only. The bank or tool the row was synced from keeps its own category: no call reaches it. When the person named that bank or tool, say that it stays unchanged.

When the token authorizes one workspace, call this directly — no workspace call is needed first. When it authorizes several, this read will not guess which one you mean: pass workspace_id on the call.

Input Schema

NameRequiredDescriptionDefault
categoryYesThe category to store: a label of this list, or the short name the list shows for it (for example "Bank fees" for Bank, FX & payment-processing fees). null sets the rows back to no category.
decisionNoHow the user arrived at the label. Omit for a user choice. See the description before sending accepted_classifier_suggestion.
workspace_idNoTarget workspace. Omit when the token authorizes one workspace. Required when it authorizes several — a write lands in exactly one workspace and this call would not say which.
confirm_firstNoTrue when the person's message asks that nothing change before they approve. In Well's own sessions the call then waits on Well's card. Elsewhere it changes nothing and returns awaiting_confirmation: ask the person in the conversation, then call again without it. Omit otherwise.
conversation_idNoThe conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.
idempotency_keyNoOptional client-supplied key. A retried write with the same key returns the original result instead of re-applying the operation.
transaction_idsYesEvery transaction that takes this label, from a read. One call for all of them, never one call per row.
workspace_scopeNoRestrict this call to the family: the token's primary workspace plus the company workspaces under it that you are a member of. Unrelated authorized workspaces drop out of the read; row reads merge across the family, while a per-workspace figure still reports exactly one workspace.

Output Schema

NameRequiredDescriptionDefault
errorNo
failedNoHow many rows were not changed. Each one is in results with its error.
totalsNoThe saved rows summed per currency, signed as the bank books them. Absent when a saved row came back without its amount.
resultsNoOne entry per row, in the order of transaction_ids.
successYesTrue when every row of the call was written.
writtenNoHow many rows now carry the label.
categoryNoThe label stored on the written rows.
error_codeNoThe typed reason of a refusal, when the server gave one. "TRANSACTION_CATEGORY_BOOKED_IN_LEDGER": the category is booked in the ledger, so it cannot be set back to no category.
refusal_reasonNoSet when the category is not in this workspace's list. Nothing was written.
category_sourceNoProvenance the server settled on: "classifier" when it accepted the affirmation claim, "user" when the caller picked the label or the claim was downgraded.
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
newly_connectedNoTools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. The connected-line rule says when to tell an entry; when it does, one line per entry, in the person's language: "<name> is connected.". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later.
transaction_idsNoThe distinct rows the call named.
allowed_categoriesNoWith refusal_reason: the categories this workspace files and is offered. Send `label` back; `name` is how the workspace shows it.
category_confidenceNoThe classifier's score, preserved only on an accepted affirmation. Null on a user choice.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
awaiting_confirmationNoTrue when the call asked to wait for the person's approval and nothing was changed.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed10 schema fields changed
    • removedOutput schema / properties / category / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / category / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / category_confidence / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / category_confidence / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / category_source / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / category_source / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / results / items / properties / category_confidence / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / results / items / properties / category_confidence / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / results / items / properties / category_source / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / results / items / properties / category_source / type
      Added value: +[
      +  "string",
      +  "null"
      +]
  2. Changed2 schema fields changed
    • changedInput schema / properties / category / anyOf
      Previous value: -[
      -  {
      -    "enum": [
      -      "Subscription & product revenue",
      -      "Services & implementation revenue",
      -      "Grants & subsidies (non-repayable)",
      -      "Research tax credit (CIR/CICE)",
      -      "Other operating income & cashback",
      -      "Employee salaries (net)",
      -      "Employer social charges",
      -      "Wage withholding remittance (PAS)",
      -      "Employee benefits & insurance",
      -      "Recruiting & hiring fees",
      -      "Engineering / product contractors",
      -      "Operations / GTM / other contractors",
      -      "Legal & corporate-secretarial fees",
      -      "Accounting, finance & advisory fees",
      -      "Other professional & consulting fees",
      -      "AI model / inference (cost of revenue)",
      -      "Hosting, infrastructure & data (cost of revenue)",
      -      "Developer & engineering software",
      -      "Business & productivity software",
      -      "Hardware & equipment",
      -      "Advertising & paid media",
      -      "Sales / marketing tools & lead-gen",
      -      "AI creative & content production",
      -      "Office rent & coworking",
      -      "Office supplies & general operations",
      -      "Travel & transport",
      -      "Meals & entertainment",
      -      "Team events & offsites",
      -      "Bank, FX & payment-processing fees",
      -      "Business insurance",
      -      "Corporate income tax",
      -      "Local & business taxes",
      -      "Other operating expense (residual)",
      -      "VAT receivable (input / refund)",
      -      "VAT payable (output)",
      -      "Treasury placement (out)",
      -      "Treasury redemption (in)",
      -      "Investment & interest income",
      -      "Equity proceeds & raise costs",
      -      "Loan drawdowns & repayments",
      -      "Repayable public advances",
      -      "Realised FX gain / loss",
      -      "Inter-account transfer (same entity)",
      -      "Inter-company transfer (own entities)",
      -      "FX conversion principal",
      -      "Uncategorised / suspense",
      -      "Groceries",
      -      "Rent & mortgage",
      -      "Energy, water, internet & phone",
      -      "Health",
      -      "Personal insurance",
      -      "Shopping & clothing",
      -      "Leisure, culture & sport",
      -      "Subscriptions & streaming",
      -      "Childcare & education",
      -      "Gifts & donations",
      -      "Personal taxes",
      -      "Cash withdrawals",
      -      "Salary",
      -      "Other income",
      -      "Other household expenses"
      -    ],
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "enum": [
      +      "Subscription & product revenue",
      +      "Services & implementation revenue",
      +      "Grants & subsidies (non-repayable)",
      +      "Research tax credit (CIR/CICE)",
      +      "Other operating income & cashback",
      +      "Employee salaries (net)",
      +      "Employer social charges",
      +      "Wage withholding remittance (PAS)",
      +      "Employee benefits & insurance",
      +      "Recruiting & hiring fees",
      +      "Engineering / product contractors",
      +      "Operations / GTM / other contractors",
      +      "Legal & corporate-secretarial fees",
      +      "Accounting, finance & advisory fees",
      +      "Other professional & consulting fees",
      +      "AI model / inference (cost of revenue)",
      +      "Hosting, infrastructure & data (cost of revenue)",
      +      "Developer & engineering software",
      +      "Business & productivity software",
      +      "Hardware & equipment",
      +      "Advertising & paid media",
      +      "Sales / marketing tools & lead-gen",
      +      "AI creative & content production",
      +      "Office rent & coworking",
      +      "Office supplies & general operations",
      +      "Travel & transport",
      +      "Meals & entertainment",
      +      "Team events & offsites",
      +      "Bank, FX & payment-processing fees",
      +      "Business insurance",
      +      "Corporate income tax",
      +      "Local & business taxes",
      +      "Other operating expense (residual)",
      +      "VAT receivable (input / refund)",
      +      "VAT payable (output)",
      +      "Treasury placement (out)",
      +      "Treasury redemption (in)",
      +      "Investment & interest income",
      +      "Equity proceeds & raise costs",
      +      "Loan drawdowns & repayments",
      +      "Repayable public advances",
      +      "Realised FX gain / loss",
      +      "Inter-account transfer (same entity)",
      +      "Inter-company transfer (own entities)",
      +      "FX conversion principal",
      +      "Uncategorised / suspense",
      +      "Goods purchased",
      +      "Banking services",
      +      "Groceries",
      +      "Rent & mortgage",
      +      "Energy, water, internet & phone",
      +      "Health",
      +      "Personal insurance",
      +      "Shopping & clothing",
      +      "Leisure, culture & sport",
      +      "Subscriptions & streaming",
      +      "Childcare & education",
      +      "Gifts & donations",
      +      "Personal taxes",
      +      "Cash withdrawals",
      +      "Salary",
      +      "Other income",
      +      "Other household expenses",
      +      "Subscription revenue",
      +      "Ventes abonnements",
      +      "Services revenue",
      +      "Ventes de services",
      +      "Grants and subsidies",
      +      "Subventions",
      +      "Research tax credit",
      +      "Crédit d'impôt CIR/CICE",
      +      "Other income, cashback",
      +      "Autres revenus, cashback",
      +      "Net salaries",
      +      "Salaires nets",
      +      "Social charges",
      +      "Charges sociales",
      +      "Wage withholding tax",
      +      "Prélèvement à la source",
      +      "Employee benefits",
      +      "Avantages salariés",
      +      "Recruiting",
      +      "Recrutement",
      +      "Tech/product contractors",
      +      "Freelances tech/produit",
      +      "Ops/sales contractors",
      +      "Freelances ops/ventes",
      +      "Legal",
      +      "Juridique",
      +      "Accounting, advisory",
      +      "Comptabilité, conseil",
      +      "Other professional fees",
      +      "Autres honoraires",
      +      "AI inference (cost)",
      +      "IA, inférence (coût)",
      +      "Hosting, infrastructure",
      +      "Hébergement, infra",
      +      "Developer software",
      +      "Logiciels tech",
      +      "Productivity software",
      +      "Logiciels bureautique",
      +      "Hardware",
      +      "Matériel",
      +      "Advertising",
      +      "Publicité",
      +      "Sales/marketing tools",
      +      "Outils ventes/marketing",
      +      "AI content, creative",
      +      "Contenu, création IA",
      +      "Rent, coworking",
      +      "Loyer, coworking",
      +      "Office supplies",
      +      "Fournitures de bureau",
      +      "Travel, transport",
      +      "Voyages, transport",
      +      "Transport, travel",
      +      "Transports, voyages",
      +      "Meals, entertainment",
      +      "Repas, réceptions",
      +      "Restaurants, bars",
      +      "Team events",
      +      "Événements d'équipe",
      +      "Bank fees",
      +      "Frais bancaires",
      +      "Insurance",
      +      "Assurances",
      +      "Impôt sur les sociétés",
      +      "Local taxes",
      +      "Taxes locales",
      +      "Other expenses",
      +      "Autres charges",
      +      "VAT receivable",
      +      "TVA à récupérer",
      +      "VAT payable",
      +      "TVA à payer",
      +      "Cash placement",
      +      "Placement trésorerie",
      +      "Savings (out)",
      +      "Épargne (versement)",
      +      "Placement withdrawal",
      +      "Retrait de placement",
      +      "Savings (in)",
      +      "Épargne (retrait)",
      +      "Interest, investments",
      +      "Intérêts, placements",
      +      "Interest",
      +      "Intérêts",
      +      "Fundraising",
      +      "Levée de fonds",
      +      "Loans",
      +      "Emprunts",
      +      "Loans, credit",
      +      "Prêts, crédits",
      +      "Repayable advances",
      +      "Avances remboursables",
      +      "FX gain/loss",
      +      "Gain/perte de change",
      +      "Internal transfer",
      +      "Virement entre comptes",
      +      "Between my accounts",
      +      "Entre mes comptes",
      +      "Intra-group transfer",
      +      "Virement intra-groupe",
      +      "Currency conversion",
      +      "Conversion de devises",
      +      "Currency exchange",
      +      "Change de devises",
      +      "Uncategorized",
      +      "Non catégorisé",
      +      "Uncategorised",
      +      "Achats de marchandises",
      +      "Services bancaires",
      +      "Courses alimentaires",
      +      "Rent, mortgage",
      +      "Loyer, crédit immo",
      +      "Energy, water, telecom",
      +      "Énergie, eau, télécoms",
      +      "Santé",
      +      "Assurances perso",
      +      "Shopping, clothing",
      +      "Shopping, vêtements",
      +      "Leisure, culture, sport",
      +      "Loisirs, culture, sport",
      +      "Subscriptions, streaming",
      +      "Abonnements, streaming",
      +      "Childcare, education",
      +      "Enfants, scolarité",
      +      "Gifts, donations",
      +      "Cadeaux, dons",
      +      "Impôts personnels",
      +      "Retraits d'espèces",
      +      "Salaire",
      +      "Autres revenus",
      +      "Autres dépenses"
      +    ],
      +    "type": "string"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • changedInput schema / properties / category / description
      Previous value: -"The category label to store, one of the labels in this list. null sets the rows back to no category."New value: +"The category to store: a label of this list, or the short name the list shows for it (for example \"Bank fees\" for Bank, FX & payment-processing fees). null sets the rows back to no category."
  3. Changed1 schema field changed
    • addedOutput schema / properties / totals
      Added value: +{
      +  "description": "The saved rows summed per currency, signed as the bank books them. Absent when a saved row came back without its amount.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "amount": {
      +        "description": "The sum of the saved rows in this currency, signed, to the cent.",
      +        "type": "number"
      +      },
      +      "currency": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "currency",
      +      "amount"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
  4. Changed11 schema fields changed
    • changedInput schema / properties / category / description
      Previous value: -"The category label to store, one of the labels in this list. null sets the row back to no category."New value: +"The category label to store, one of the labels in this list. null sets the rows back to no category."
    • removedInput schema / properties / transaction_id
      Removed value: -{
      -  "description": "The transaction to categorize, from well_list_uncategorized_window.",
      -  "format": "uuid",
      -  "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
      -  "type": "string"
      -}
    • addedInput schema / properties / transaction_ids
      Added value: +{
      +  "description": "Every transaction that takes this label, from a read. One call for all of them, never one call per row.",
      +  "items": {
      +    "format": "uuid",
      +    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
      +    "type": "string"
      +  },
      +  "maxItems": 50,
      +  "minItems": 1,
      +  "type": "array"
      +}
    • changedInput schema / required
      Previous value: -[
      -  "transaction_id",
      -  "category"
      -]New value: +[
      +  "transaction_ids",
      +  "category"
      +]
    • changedOutput schema / properties / category / description
      Previous value: -"The label stored on the row after the write."New value: +"The label stored on the written rows."
    • addedOutput schema / properties / failed
      Added value: +{
      +  "description": "How many rows were not changed. Each one is in results with its error.",
      +  "maximum": 9007199254740991,
      +  "minimum": -9007199254740991,
      +  "type": "integer"
      +}
    • addedOutput schema / properties / results
      Added value: +{
      +  "description": "One entry per row, in the order of transaction_ids.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "category_confidence": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "The classifier's score, preserved only on an accepted affirmation. Null on a user choice."
      +      },
      +      "category_source": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "Provenance the server settled on: \"classifier\" when it accepted the affirmation claim, \"user\" when the caller picked the label or the claim was downgraded."
      +      },
      +      "error": {
      +        "type": "string"
      +      },
      +      "error_code": {
      +        "description": "The typed reason of a refusal, when the server gave one. \"TRANSACTION_CATEGORY_BOOKED_IN_LEDGER\": the category is booked in the ledger, so it cannot be set back to no category.",
      +        "type": "string"
      +      },
      +      "success": {
      +        "type": "boolean"
      +      },
      +      "transaction_id": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "transaction_id",
      +      "success"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / properties / success / description
      Added value: +"True when every row of the call was written."
    • removedOutput schema / properties / transaction_id
      Removed value: -{
      -  "type": "string"
      -}
    • addedOutput schema / properties / transaction_ids
      Added value: +{
      +  "description": "The distinct rows the call named.",
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / properties / written
      Added value: +{
      +  "description": "How many rows now carry the label.",
      +  "maximum": 9007199254740991,
      +  "minimum": -9007199254740991,
      +  "type": "integer"
      +}
  5. Changed1 schema field changed
    • changedOutput schema / properties / newly_connected / description
      Previous value: -"Tools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. Start the reply with one line per entry, in the person's language: \"<name> is connected.\". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later."New value: +"Tools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. The connected-line rule says when to tell an entry; when it does, one line per entry, in the person's language: \"<name> is connected.\". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later."
  6. Changed9 schema fields changed
    • addedInput schema / properties / category / anyOf
      Added value: +[
      +  {
      +    "enum": [
      +      "Subscription & product revenue",
      +      "Services & implementation revenue",
      +      "Grants & subsidies (non-repayable)",
      +      "Research tax credit (CIR/CICE)",
      +      "Other operating income & cashback",
      +      "Employee salaries (net)",
      +      "Employer social charges",
      +      "Wage withholding remittance (PAS)",
      +      "Employee benefits & insurance",
      +      "Recruiting & hiring fees",
      +      "Engineering / product contractors",
      +      "Operations / GTM / other contractors",
      +      "Legal & corporate-secretarial fees",
      +      "Accounting, finance & advisory fees",
      +      "Other professional & consulting fees",
      +      "AI model / inference (cost of revenue)",
      +      "Hosting, infrastructure & data (cost of revenue)",
      +      "Developer & engineering software",
      +      "Business & productivity software",
      +      "Hardware & equipment",
      +      "Advertising & paid media",
      +      "Sales / marketing tools & lead-gen",
      +      "AI creative & content production",
      +      "Office rent & coworking",
      +      "Office supplies & general operations",
      +      "Travel & transport",
      +      "Meals & entertainment",
      +      "Team events & offsites",
      +      "Bank, FX & payment-processing fees",
      +      "Business insurance",
      +      "Corporate income tax",
      +      "Local & business taxes",
      +      "Other operating expense (residual)",
      +      "VAT receivable (input / refund)",
      +      "VAT payable (output)",
      +      "Treasury placement (out)",
      +      "Treasury redemption (in)",
      +      "Investment & interest income",
      +      "Equity proceeds & raise costs",
      +      "Loan drawdowns & repayments",
      +      "Repayable public advances",
      +      "Realised FX gain / loss",
      +      "Inter-account transfer (same entity)",
      +      "Inter-company transfer (own entities)",
      +      "FX conversion principal",
      +      "Uncategorised / suspense",
      +      "Groceries",
      +      "Rent & mortgage",
      +      "Energy, water, internet & phone",
      +      "Health",
      +      "Personal insurance",
      +      "Shopping & clothing",
      +      "Leisure, culture & sport",
      +      "Subscriptions & streaming",
      +      "Childcare & education",
      +      "Gifts & donations",
      +      "Personal taxes",
      +      "Cash withdrawals",
      +      "Salary",
      +      "Other income",
      +      "Other household expenses"
      +    ],
      +    "type": "string"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • changedInput schema / properties / category / description
      Previous value: -"The category label to store. Must be one of the labels in this list."New value: +"The category label to store, one of the labels in this list. null sets the row back to no category."
    • removedInput schema / properties / category / enum
      Removed value: -[
      -  "Subscription & product revenue",
      -  "Services & implementation revenue",
      -  "Grants & subsidies (non-repayable)",
      -  "Research tax credit (CIR/CICE)",
      -  "Other operating income & cashback",
      -  "Employee salaries (net)",
      -  "Employer social charges",
      -  "Wage withholding remittance (PAS)",
      -  "Employee benefits & insurance",
      -  "Recruiting & hiring fees",
      -  "Engineering / product contractors",
      -  "Operations / GTM / other contractors",
      -  "Legal & corporate-secretarial fees",
      -  "Accounting, finance & advisory fees",
      -  "Other professional & consulting fees",
      -  "AI model / inference (cost of revenue)",
      -  "Hosting, infrastructure & data (cost of revenue)",
      -  "Developer & engineering software",
      -  "Business & productivity software",
      -  "Hardware & equipment",
      -  "Advertising & paid media",
      -  "Sales / marketing tools & lead-gen",
      -  "AI creative & content production",
      -  "Office rent & coworking",
      -  "Office supplies & general operations",
      -  "Travel & transport",
      -  "Meals & entertainment",
      -  "Team events & offsites",
      -  "Bank, FX & payment-processing fees",
      -  "Business insurance",
      -  "Corporate income tax",
      -  "Local & business taxes",
      -  "Other operating expense (residual)",
      -  "VAT receivable (input / refund)",
      -  "VAT payable (output)",
      -  "Treasury placement (out)",
      -  "Treasury redemption (in)",
      -  "Investment & interest income",
      -  "Equity proceeds & raise costs",
      -  "Loan drawdowns & repayments",
      -  "Repayable public advances",
      -  "Realised FX gain / loss",
      -  "Inter-account transfer (same entity)",
      -  "Inter-company transfer (own entities)",
      -  "FX conversion principal",
      -  "Uncategorised / suspense"
      -]
    • removedInput schema / properties / category / type
      Removed value: -"string"
    • addedInput schema / properties / confirm_first
      Added value: +{
      +  "description": "True when the person's message asks that nothing change before they approve. In Well's own sessions the call then waits on Well's card. Elsewhere it changes nothing and returns awaiting_confirmation: ask the person in the conversation, then call again without it. Omit otherwise.",
      +  "type": "boolean"
      +}
    • addedOutput schema / properties / allowed_categories
      Added value: +{
      +  "description": "With refusal_reason: the categories this workspace files and is offered. Send `label` back; `name` is how the workspace shows it.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "key": {
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "name": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "key",
      +      "label",
      +      "name"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / properties / awaiting_confirmation
      Added value: +{
      +  "description": "True when the call asked to wait for the person's approval and nothing was changed.",
      +  "type": "boolean"
      +}
    • addedOutput schema / properties / error_code
      Added value: +{
      +  "description": "The typed reason of a refusal, when the server gave one. \"TRANSACTION_CATEGORY_BOOKED_IN_LEDGER\": the category is booked in the ledger, so it cannot be set back to no category.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / refusal_reason
      Added value: +{
      +  "const": "TRANSACTION_CATEGORY_OUTSIDE_WORKSPACE_AUDIENCE",
      +  "description": "Set when the category is not in this workspace's list. Nothing was written.",
      +  "type": "string"
      +}
  7. Changed1 schema field changed
    • addedInput schema / properties / workspace_scope
      Added value: +{
      +  "description": "Restrict this call to the family: the token's primary workspace plus the company workspaces under it that you are a member of. Unrelated authorized workspaces drop out of the read; row reads merge across the family, while a per-workspace figure still reports exactly one workspace.",
      +  "enum": [
      +    "family"
      +  ],
      +  "type": "string"
      +}
  8. Changed1 schema field changed
    • addedOutput schema / properties / newly_connected
      Added value: +{
      +  "description": "Tools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. Start the reply with one line per entry, in the person's language: \"<name> is connected.\". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "name": {
      +        "type": "string"
      +      },
      +      "service_id": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "name",
      +      "service_id"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
  9. Changed4 schema fields changed
    • addedInput schema / properties / conversation_id
      Added value: +{
      +  "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / conversation_id
      Added value: +{
      +  "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / conversation_id_note
      Added value: +{
      +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / conversation_id_source
      Added value: +{
      +  "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.",
      +  "enum": [
      +    "host_meta",
      +    "argument",
      +    "minted"
      +  ],
      +  "type": "string"
      +}
  10. Added

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only say readOnly=false, destructive=false, openWorld=false; the description goes far beyond by explaining the decision provenance model, that the server downgrades a false accepted_classifier_suggestion claim, that an uncategorized-window row always downgrades, the TRANSACTION_CATEGORY_BOOKED_IN_LEDGER refusal for null, that null means 'no category' is sticky, that effect on exemption matching changes while internal-transfer rules do not, that the write never reaches the source bank, and that API-key connections cannot write.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and bolded section headers make it scannable despite its length. It is, however, unusually long and repeats the accepted_classifier_suggestion rule across three separate passages, which costs some efficiency even though nearly every sentence carries operational content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 8-parameter mutation with an output schema, the description covers the gaps the schema cannot: auth requirements, workspace scoping, the confirm_first round-trip, per-row partial failure reporting, and the exact read-back phrasing. Even the return shape (written/failed/results/totals) is characterized even though an output schema exists.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so a 3 would be the floor, but the description adds real meaning beyond the schema: what `decision` values imply for the row's stored category_source and confidence, that omitting `decision` defaults to user_choice, that `category: null` clears rather than sets suspense, the 50-id batching rule for `transaction_ids`, and the confirm_first/workspace_id/conversation_id behaviors.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource+scope: 'Set the category of one or more transactions to ONE label' and frames the write as the thing that 'clears a categorization gate'. It is distinguishable from nearby siblings such as well_update_transaction_category_rule, well_set_counterparty_default_category, and well_update_transaction, none of which it could be confused with.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly covers when to call, when NOT to call (an 'ok' reply answering a proposal names no row; an ambiguous label matching two categories is a question, never a pick), which read to use first (well_list_uncategorized_window), and how to batch vs. split calls. It names the alternative surface and the condition that selects it in every case.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources