Skip to main content
Glama

List counterparties

well_list_counterparties
Read-only

List the workspace's counterparty companies and how each one is CATEGORIZED — the company-level industry labels a counterparty carries. Use it for "which suppliers have no category?", "what industries are my counterparties in?", and before categorizing a counterparty so you name real ids instead of guessing.

Name a scope, and say whether to keep only the ones missing a category:

  • periods: [{ calendar_year, calendar_month }, …] (1-12): the counterparties whose invoices those months are still missing, categorized ones included, each row tagged with its month and carrying tx_count, base_total_amount in base_currency, and suggested_retrieval. Every month must have ended.

  • periods PLUS uncategorized_only: true: the same months, keeping ONLY the counterparties that carry no category. Use this whenever the question is which of a period's suppliers still need one, and whenever a step asks the user to categorize them: the categorized ones are not the work, and listing them buries it.

  • uncategorized_only: true alone: a WORKSPACE-WIDE sweep for every counterparty that carries no category, no month involved. Returns 50 rows per page plus total_count; tx_count, base_total_amount and suggested_retrieval are null because the call names no period. When next_cursor is not null the sweep has more counterparties: call again with cursor set to it to read them. It is a POSITION, not a row offset, so categorizing the rows of one page never hides the rows of the next. Only this sweep pages: cursor is refused beside periods.

  • missing_ledger_only: true alone: the LEDGER-ASSIGNMENT worklist — the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take. Its rows ride in ledger_rows, not rows, each carrying needs_payable/needs_receivable and the AP/AR default it holds now; a needed slot is empty, assigned from the chart of accounts. When the classifier proposed an account the person has not confirmed, the row carries it in suggested_payable_default/suggested_receivable_default (account, confidence, reasoning, memory_informed): show it, and only after the person agrees, set the slot to account.id, which confirms the proposal. This is a DIFFERENT question from categorization: it assigns a ledger account, not an industry label. Set a default with well_update_company({ account_payable_default_id | account_receivable_default_id }); read the account ids with well_list_ledger_accounts. It returns the first 500 counterparties needing a default, so a worklist that fills 500 (total_count equal to row_count at 500) is a FLOOR: assign those and read the scope again for the rest. It is its own scope — never combine it with periods, uncategorized_only, or cursor.

COST: the period form has no batch endpoint, so each named month is a separate read of that month's spend. Ask for the months the user actually named, not a whole year "to be safe".

Every row carries categories ([{ category_id, name }]) and is_categorized. categorized_count and uncategorized_count count the COUNTERPARTIES OF THE SCOPE, once each however many months they appear in, not the rows returned. Under uncategorized_only the result lists the uncategorized ones alone while categorized_count still counts the ones it withheld, so the two together are the period's coverage and uncategorized_count is the work left. Report both: naming the listed rows as the period's whole counterparty set overstates how much is uncategorized.

TO SET a counterparty's categories, call well_update_company({ company_id, category_ids: [...] }) — that field REPLACES the company's whole set. Read the available labels first with well_query_records({ root: "categories", whereClause: { category_type: { _eq: "company" } } }): that is the company-category catalog. It has no curated allowlist — the labels are minted during enrichment — so pass ids from it rather than inventing a taxonomy.

suggested_retrieval is derived from the PROVIDER match, not from the category. Categorizing a counterparty does not change it; do not tell the user otherwise.

This tool only reads. It categorizes nothing, mints no task, connects nothing and fetches no invoice.

No workspace read is needed first: the workspace is resolved from the caller's authorized token, same as every other well_* tool.

When the user asks to see, list or fix these rows, call this tool directly: its card is the answer. ⚠️ This tool draws its card on EVERY call, the empty one included. So when nobody asked for the list and you only need to CHECK whether anything is left (the first pass of a gate, or a re-check after a repair), call well_get_worklist_status({ worklist: "counterparties_to_categorize", periods }) first, with the same scope you would pass here. It draws nothing. Call this tool after it only when it answers open: true.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cursorNoThe next page of the workspace-wide uncategorized sweep: pass back the `next_cursor` the previous call returned. Only that sweep pages, so this needs `uncategorized_only: true` and NO `periods`, because a period scope returns every month it covers in one call.
periodsNoThe calendar months whose counterparties to list, 1-12. Each month costs one separate read of that month's spend. Duplicates are refused.
workspace_idNoTarget workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did.
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.
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.
uncategorized_onlyNoKeep only the counterparties that carry no industry category. WITH `periods`: the uncategorized counterparties OF those months. Use it whenever the question is which of a period's suppliers still need a category. WITHOUT `periods`: a WORKSPACE-WIDE sweep for every uncategorized counterparty, 50 rows per page plus the total, with a `next_cursor` for the page after this one.
missing_ledger_onlyNoA workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable`, the AP/AR defaults it holds now (a needed slot is empty), and `suggested_payable_default`/`suggested_receivable_default` when the classifier proposed an account for a needed slot; it rides in `ledger_rows` rather than `rows`.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
modeNoWhich SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones. `missing_ledger_only` returns its rows in `ledger_rows`, not `rows`.
rowsYes
errorNo
hintsNo
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
row_countNo
ledger_rowsNo
next_cursorNoThe uncategorized sweep's next page: pass it back as `cursor`. Null when this page ends the sweep, absent on the periods scope, which pages nothing.
total_countNoCounterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`. Under `missing_ledger_only` it is the worklist actually returned and equals `row_count`: that scope reads the first 500 counterparties needing a default, so a worklist that fills 500 is a FLOOR, not a complete count. Assign those and read the scope again for the rest.
workspace_idNo
base_currencyNo
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.
periods_coveredNoThe months the result covers, oldest first.
categorized_countNoCOUNTERPARTIES of the scope that carry at least one category, counted over the whole scope, not over `rows`, and counted once however many months a counterparty appears in. Under `uncategorized_only` these are exactly the counterparties the result withheld, so a non-zero figure beside rows that are all uncategorized is the coverage, not a contradiction.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
uncategorized_onlyNoWhether `rows` holds ONLY the counterparties that carry no category. True on the workspace-wide sweep and on a period scope the call filtered. When true, `categorized_count` counts counterparties the result did NOT list.
uncategorized_countNoCOUNTERPARTIES of the scope that carry none, counted the same way. This is the outstanding work; with `categorized_count` it is the scope's whole population.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
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. 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."
  2. 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"
      +}
  3. 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"
      +}
  4. Changed1 schema field changed
    • changedInput schema / properties / missing_ledger_only / description
      Previous value: -"A workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable` and the AP/AR defaults it holds now (a needed slot is empty), and rides in `ledger_rows` rather than `rows`."New value: +"A workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable`, the AP/AR defaults it holds now (a needed slot is empty), and `suggested_payable_default`/`suggested_receivable_default` when the classifier proposed an account for a needed slot; it rides in `ledger_rows` rather than `rows`."
  5. Changed9 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"
      +}
    • addedInput schema / properties / missing_ledger_only
      Added value: +{
      +  "const": true,
      +  "description": "A workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable` and the AP/AR defaults it holds now (a needed slot is empty), and rides in `ledger_rows` rather than `rows`.",
      +  "type": "boolean"
      +}
    • 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"
      +}
    • addedOutput schema / properties / ledger_rows
      Added value: +{
      +  "items": {
      +    "additionalProperties": {},
      +    "propertyNames": {
      +      "type": "string"
      +    },
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • changedOutput schema / properties / mode / description
      Previous value: -"Which SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones."New value: +"Which SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones. `missing_ledger_only` returns its rows in `ledger_rows`, not `rows`."
    • changedOutput schema / properties / mode / enum
      Previous value: -[
      -  "periods",
      -  "uncategorized_only"
      -]New value: +[
      +  "periods",
      +  "uncategorized_only",
      +  "missing_ledger_only"
      +]
    • changedOutput schema / properties / total_count / description
      Previous value: -"Counterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`."New value: +"Counterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`. Under `missing_ledger_only` it is the worklist actually returned and equals `row_count`: that scope reads the first 500 counterparties needing a default, so a worklist that fills 500 is a FLOOR, not a complete count. Assign those and read the scope again for the rest."
  6. Changed1 schema field changed
    • addedOutput schema / properties / next_step
      Added value: +{
      +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
      +  "type": "string"
      +}
  7. Changed9 schema fields changed
    • changedInput schema / properties / cursor / description
      Previous value: -"The next page of the uncategorized sweep: pass back the `next_cursor` the previous call returned. Only the sweep pages, so this needs `uncategorized_only: true`."New value: +"The next page of the workspace-wide uncategorized sweep: pass back the `next_cursor` the previous call returned. Only that sweep pages, so this needs `uncategorized_only: true` and NO `periods`, because a period scope returns every month it covers in one call."
    • changedInput schema / properties / uncategorized_only / description
      Previous value: -"Instead of a period, sweep the WHOLE workspace for counterparties that carry no industry category. Returns 50 rows per page plus the total, and a `next_cursor` for the page after this one."New value: +"Keep only the counterparties that carry no industry category. WITH `periods`: the uncategorized counterparties OF those months. Use it whenever the question is which of a period's suppliers still need a category. WITHOUT `periods`: a WORKSPACE-WIDE sweep for every uncategorized counterparty, 50 rows per page plus the total, with a `next_cursor` for the page after this one."
    • changedOutput schema / properties / categorized_count / description
      Previous value: -"Rows in this result that carry at least one category."New value: +"COUNTERPARTIES of the scope that carry at least one category, counted over the whole scope, not over `rows`, and counted once however many months a counterparty appears in. Under `uncategorized_only` these are exactly the counterparties the result withheld, so a non-zero figure beside rows that are all uncategorized is the coverage, not a contradiction."
    • changedOutput schema / properties / mode / description
      Previous value: -"Which scope the call asked for; the row fields that are populated follow from it."New value: +"Which SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones."
    • addedOutput schema / properties / rows / items / properties / suggested_categories
      Added value: +{
      +  "description": "A stored classifier PROPOSAL is a candidate label, never a decision: `categories` is what this counterparty actually carries, and only a write makes a proposal true. Highest confidence first, at most three. Empty when the classifier has not run, abstained, or a human already resolved its proposal — an empty array is not evidence that no label fits. Present on both scopes. Report a proposal as a suggestion to the user, never as the counterparty's industry, and NOTE this is unrelated to `suggested_retrieval`, which is about fetching invoices.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "category_id": {
      +        "description": "The catalog id of the proposed label — pass it straight to `well_update_company` to accept it.",
      +        "type": "string"
      +      },
      +      "confidence": {
      +        "description": "0..1, the classifier's own scale.",
      +        "type": "number"
      +      },
      +      "name": {
      +        "type": "string"
      +      },
      +      "rank": {
      +        "description": "1 is the classifier's top proposal.",
      +        "maximum": 9007199254740991,
      +        "minimum": -9007199254740991,
      +        "type": "integer"
      +      },
      +      "reasoning": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "The classifier's own account of this proposal, or null when the run stored none."
      +      }
      +    },
      +    "required": [
      +      "category_id",
      +      "name",
      +      "confidence",
      +      "rank",
      +      "reasoning"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • changedOutput schema / properties / rows / items / required
      Previous value: -[
      -  "company_id",
      -  "name",
      -  "domain",
      -  "logo_url",
      -  "calendar_year",
      -  "calendar_month",
      -  "period_label",
      -  "tx_count",
      -  "base_total_amount",
      -  "categories",
      -  "is_categorized",
      -  "suggested_retrieval"
      -]New value: +[
      +  "company_id",
      +  "name",
      +  "domain",
      +  "logo_url",
      +  "calendar_year",
      +  "calendar_month",
      +  "period_label",
      +  "tx_count",
      +  "base_total_amount",
      +  "categories",
      +  "is_categorized",
      +  "suggested_categories",
      +  "suggested_retrieval"
      +]
    • changedOutput schema / properties / total_count / description
      Previous value: -"Matching counterparties BEFORE the row cap, so a capped sweep says what it left out. On the periods scope: DISTINCT counterparties across the window — a multi-month call lists one counterparty on one row per month, so row_count can exceed it."New value: +"Counterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`."
    • changedOutput schema / properties / uncategorized_count / description
      Previous value: -"Rows in this result that carry none."New value: +"COUNTERPARTIES of the scope that carry none, counted the same way. This is the outstanding work; with `categorized_count` it is the scope's whole population."
    • addedOutput schema / properties / uncategorized_only
      Added value: +{
      +  "description": "Whether `rows` holds ONLY the counterparties that carry no category. True on the workspace-wide sweep and on a period scope the call filtered. When true, `categorized_count` counts counterparties the result did NOT list.",
      +  "type": "boolean"
      +}
  8. Changed5 schema fields changed
    • addedInput schema / properties / cursor
      Added value: +{
      +  "description": "The next page of the uncategorized sweep: pass back the `next_cursor` the previous call returned. Only the sweep pages, so this needs `uncategorized_only: true`.",
      +  "type": "string"
      +}
    • changedInput schema / properties / uncategorized_only / description
      Previous value: -"Instead of a period, sweep the WHOLE workspace for counterparties that carry no industry category. Returns at most 50 rows plus the total."New value: +"Instead of a period, sweep the WHOLE workspace for counterparties that carry no industry category. Returns 50 rows per page plus the total, and a `next_cursor` for the page after this one."
    • changedInput schema / properties / workspace_id / description
      Previous value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did."
    • addedOutput schema / properties / next_cursor
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "The uncategorized sweep's next page: pass it back as `cursor`. Null when this page ends the sweep, absent on the periods scope, which pages nothing."
      +}
    • addedOutput schema / properties / resolved_workspace
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "The workspace that answered, when the caller named none and the token authorizes several.",
      +  "properties": {
      +    "name": {
      +      "anyOf": [
      +        {
      +          "type": "string"
      +        },
      +        {
      +          "type": "null"
      +        }
      +      ]
      +    },
      +    "workspace_id": {
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "workspace_id",
      +    "name"
      +  ],
      +  "type": "object"
      +}
  9. Added

TDQS

Score is being calculated.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources