MCP server

Connect AI tools to Flowtly through the Model Context Protocol at mcp.flowtly.eu.

Connect

claude mcp add --transport http flowtly https://mcp.flowtly.eu/mcp

On this page

Bank Accounts

Tools

bankAccounts.getGet one bank account by id — name, currency, bank, and the format its statements are imported in.
bankAccounts.listList the org's bank accounts. Filter by bank, or set hidden to include archived ones. Use it to resolve the bankAccount id that transactions.list filters on.
bankAccounts.createCreate a bank account (type, name, currency, defaultImportFormat required). Write.
bankAccounts.updateUpdate a bank account by id. Write.

Clients

Tools

clients.getGet one client by id — name, country, currency, tax id and status.
clients.listList clients (the org's customers). Filter by status, or by externalPaymentCustomerId to find the client behind a payment-provider id. Use it to resolve the client id that invoices.list, deals.list, projects.list and contracts.list all filter on.
clients.createCreate a new client record (name, country, currency, status, tinType required). Write.
clients.updateUpdate a client record by id. Write.

Config Keys

Tools

configKeys.catalogList every organization config key the backend recognises, with its type and allowed values. This is the catalog of what is configurable — read it before configs.get or configs.update rather than guessing a key name. Permission is enforced per key by the backend, so a key appearing here does not guarantee the connected user may write it.

Configs

Tools

configs.getRead one organization config value by id, where the id is a key from configKeys.catalog (e.g. organization-logo-url, organization-icon-url).
configs.updateUpdate an organization config value by id (type + name required; permission is enforced per config key by the backend). Write.

Contracts

Tools

contracts.getGet one contract by id — parties, direction, value, cyclic terms and dates.
contracts.listList contracts. Filter by direction (incoming / outgoing), counterparty, project, cyclic, name or tags. Use it to resolve the contract id that contracts.paymentScheduleLines reads and that deals.win can link a won deal to.
contracts.paymentScheduleLinesList a contract's payment schedule — the instalments it is expected to be invoiced or paid in. Pass contractId from contracts.list. This is the plan, not the actuals: compare it against transactions.list to see what has really been paid.
contracts.createCreate a contract. Write.
contracts.updateUpdate a contract by id. Write.
contracts.deleteDelete a contract by id. Write.

Cost Groups

Tools

costGroups.listList cost groups / cost centres — the buckets that costs, suppliers and incoming invoices are filed under. Use it to resolve the costGroup id that suppliers.create requires and that incoming-invoice suggestions propose.
costGroups.createCreate a cost group / cost center (name + type required). Write.
costGroups.updateUpdate a cost group / cost center's name or type by id. Write.

Counterparties

Tools

counterparties.getGet one counterparty by id.
counterparties.listList counterparties — every party the org transacts with. The supplier and client flags say which side(s) a counterparty plays, and one record can be both. This is the party on a bank transaction, so it is what incoming invoices and transactions are matched against. Filter by type, supplier, client, cyclic or budgetNeutral.

CRM Notes

Tools

crmNotes.getGet one CRM note by id.
crmNotes.listList notes written on leads and deals. Filter by lead or deal to read the running commentary on one record.
crmNotes.createAdd a note to a lead or a deal (body + exactly one of lead/deal). Author is the connected user. Write.
crmNotes.updateUpdate a CRM note’s body by id. Write.
crmNotes.deleteDelete a CRM note by id. Write.

Deal Lost Reasons

Tools

dealLostReasons.getGet one deal lost-reason by id.
dealLostReasons.listList the reasons a deal can be marked lost, in order. deals.lose requires a lostReasonId from here.

Deals

Tools

deals.getGet one deal by id — title, client, stage, amount, owner, contact, expected and actual close dates.
deals.listList deals/opportunities — the sales pipeline. Filter by status (open / won / lost), stage, owner, client, lead, or by expectedCloseDate / closedAt ranges. Amounts are minor units with an explicit currency; do not assume the org's default.
deals.createCreate a deal/opportunity (title, client, stage required; amountMinor, currency, expectedCloseDate, owner, contact optional). Write.
deals.updateUpdate a deal by id (title, stage, amount, close date, owner, contact). Moving the stage is logged automatically. Write.
deals.deleteDelete a deal by id (soft delete). Write.
deals.winMark a deal won — moves it to a won stage and stamps it closed; optional contractId links an existing contract. Write.
deals.loseMark a deal lost — requires lostReasonId (from dealLostReasons.list); optional lostReasonNote. Write.
deals.reopenReopen a won/lost deal back to open. Write.

Deal Stage Histories

Tools

dealStageHistories.getGet one deal stage-change record by id.
dealStageHistories.listList a deal's stage transitions, newest first. Filter by deal. Every deals.update that moves the stage is logged here automatically, so this is how you reconstruct how long a deal sat in each stage — the deal itself only carries its current one.

Incoming Invoices

Tools

incomingInvoices.getGet one incoming (supplier) invoice or supporting document by id, with its OCR'd fields and current match state.
incomingInvoices.listList incoming (supplier) invoices and supporting documents — the accountancy inbox. An incoming invoice IS a document attached to a bank transaction, so exists.transaction=false is how you find documents that are not yet matched to a payment. Filter also by status, relatedMonth, counterparty, project, tags, or hasDetectedProblems. Each document is fingerprinted as externalId 'upload_sha256:<sha256 of the bytes>' — hash a file and look for that externalId here BEFORE incomingInvoices.create, or you will file a duplicate.
incomingInvoices.matchCandidatesList the bank transactions that could be the payment for this incoming invoice, ranked by the backend's own matcher. Reach for it when a document has no transaction attached and you need to choose one; prefer these candidates over guessing from amounts yourself.
incomingInvoices.suggestionsRead Flowtly's own proposals for an incoming invoice — supplier match, cost group, matching bank transaction, duplicate warning. These are exactly the proposals a human sees in the app. Read them first, then apply one by id with incomingInvoices.applySuggestion, or take them all with acceptAllSuggestions. Pass refresh to recompute rather than serve the cached set.
incomingInvoices.suggestionsDebugExplain WHY an incoming invoice's suggestions came out as they did — the matcher's scoring, for diagnosing a missing or wrong suggestion. Diagnostic only; use incomingInvoices.suggestions for normal work.
incomingInvoices.createFile an incoming (supplier) invoice or supporting document into accountancy — pass the bytes as base64 with a fileName and receivedAt. Flowtly OCRs it and suggests a supplier and a matching bank transaction. The file is fingerprinted as externalId 'upload_sha256:<sha256 of the bytes>': to avoid a duplicate, hash the bytes and check incomingInvoices.list for that externalId BEFORE uploading. Write.
incomingInvoices.applySuggestionAccept one of Flowtly's own suggestions on an incoming invoice — the same proposals a human sees in the app (supplier match, cost group, matching bank transaction, duplicate warning). Read them first with incomingInvoices.suggestions, then apply one by its id. Prefer this over guessing: Flowtly's matcher, not the agent, decides what is plausible. Write.
incomingInvoices.acceptAllSuggestionsAccept every pending suggestion on an incoming invoice in one call — what a human does with the app's "accept all" button. The server applies, rebuilds, and applies again until nothing new appears: the transaction match does NOT exist until the supplier and amount are applied, so a single pass would leave the document unattached. Returns a report (what was applied, what was refused and why, and the transaction it ended up filed against). Pass dryRun to preview without writing. Never accepts supplier_create or a duplicate warning. Write.
incomingInvoices.checkEInvoicesPull any new KSeF e-invoices into the org — what the app's "Sprawdź e-faktury" button does. Call this before concluding that a supplier's invoice is missing: without it you cannot tell "the supplier never sent it" from "our sync has not run yet". Returns once the fetch is queued; re-read incomingInvoices.list afterwards to see what arrived. Write.

Invoices

Tools

invoices.getGet one outgoing (sales) invoice by id — client, rows, totals, sale and issue dates, status.
invoices.listList outgoing (sales) invoices. Filter by client, tags, search, or a saleDate range. Note that saleDate — not issue date and not creation date — is the field invoices.export filters on, so use the same one here when reconciling an export.
invoices.exportStart a zip export of ISSUED invoices for a period (from/to, both YYYY-MM-DD, inclusive) filtered on SALE DATE — not issue or creation date. Only ISSUED invoices are included; drafts and unsent invoices are excluded, but corrections ARE included. Optional client restricts to one client (id or IRI from clients.list). Max 200 invoices per export — if the period has more, narrow it (e.g. export one month at a time); a period with 0 issued invoices is rejected too. This call only enqueues the job (rendering a month can take minutes) — it does NOT return a download link. Poll invoices.exportStatus with the returned exportId until it reports "ready". Write.
invoices.exportStatusPoll the status of a zip export started by invoices.export, by exportId. Once status is "ready", the response includes downloadUrl (a short-lived signed link — expires in 1 hour, see expiresAt), filename, and byteSize; the file's bytes are never returned through this tool. If status is "failed", failureReason explains why.

Lead Contacts

Tools

leadContacts.getGet one lead contact by id.
leadContacts.listList the contact people attached to leads. Filter by lead to read one prospect's contacts, or by email to find which lead a message came from.
leadContacts.createAdd a contact person to a lead (lead + name required; email, phone, role, isPrimary optional). Write.
leadContacts.updateUpdate a lead contact by id. Write.
leadContacts.deleteDelete a lead contact by id. Write.

Lead List Memberships

Tools

leadListMemberships.getGet one lead-to-list membership by id.
leadListMemberships.listList which leads sit on which outbound prospecting lists, with each one's outreach status (contacted / replied / bounced). Filter by list, lead or status — this is how you read the state of a campaign.
leadListMemberships.createAdd a lead to an outbound list (list + lead required; status optional). Write.
leadListMemberships.updateUpdate a lead’s membership in a list — e.g. set outreach status (contacted/replied/bounced). Write.
leadListMemberships.deleteRemove a lead from an outbound list. Write.

Lead Lists

Tools

leadLists.getGet one outbound prospecting list by id.
leadLists.listList outbound prospecting lists. Use it to resolve the list id that leadListMemberships.create takes.
leadLists.createCreate an outbound prospecting list (name required). Write.
leadLists.updateUpdate an outbound list by id. Write.
leadLists.deleteDelete an outbound list by id. Write.

Lead Lost Reasons

Tools

leadLostReasons.getGet one lead lost-reason by id.
leadLostReasons.listList the reasons a lead can be marked lost, in order.

Leads

Tools

leads.dedupeCheckCheck whether a prospect is already in the CRM, using the same filters as leads.list (companyName, source, owner, …). Call this BEFORE leads.create: a duplicate lead splits the outreach history across two records, and nothing downstream will merge them for you.
leads.getGet one lead by id — company, website, source, status, owner and the client it converted to, if any.
leads.listList leads — prospect targets, before qualification. Filter by status, source, owner, client, companyName, or createdAt/closedAt ranges. A qualified lead becomes a Client plus an open Deal via leads.convert; until then it lives only here, not in clients.list.
leads.createCreate a lead (outbound/inbound prospect target; companyName, source, status, owner, linked client optional). Write.
leads.updateUpdate a lead by id (company, website, source, status, owner, linked client). Write.
leads.deleteDelete a lead by id (soft delete). Write.
leads.convertConvert a qualified lead into a Client + one contact per lead-contact + an open Deal. Requires an existing client (the lead’s client or a clientId in the body). Write.

Lead Stages

Tools

leadStages.getGet one lead stage by id.
leadStages.listList the stages a lead moves through, in order. Leads have their own stage set — deals use stages.list, which is a different thing.

Organization Mail Footer

Tools

organizationMailFooter.getRead the organization's outgoing-mail footer text (the block appended to mail Flowtly sends on the org's behalf).
organizationMailFooter.updateUpdate the organization's outgoing-mail footer text. Write.

Organizations

Tools

organizations.getGet an organization by id. WARNING — this does NOT tell you which organization you are connected to. An OAuth connection is pinned to exactly one org (token-bound), but this endpoint returns any org the connected USER is a member of, so a successful read here reads like confirmation you are working in that org when you may not be. To verify the tenant you are actually operating on, read tenant-scoped data instead — people.list or clients.list — and never start a bulk write on the strength of this call alone.

People

Tools

people.getGet one person/employee record by id — names, emails, phone, manager, and whether they are active.
people.listList people/employees. Filter by isActive, reportsTo (a manager's id), projectMembers.project, or search; page with cursor. People and employees share the same id, so this is how you resolve the employee id that work time, responsibilities, project membership and permission tools all expect.
people.createCreate a person/employee record (firstname + lastname required; optional companyEmail, contactEmail, contactPhone). Write.
people.updateUpdate a person/employee record by id (name, companyEmail, contactEmail, contactPhone, etc.). Write.
people.deleteDelete an employee/person record by id (e.g. to remove a placeholder/dummy employee). Requires ROLE_EMPLOYEES_MANAGER; the backend runs a delete processor that also detaches related records. High-impact, irreversible. Write.
people.setPermissionGroupsSet (replace) a person's permission groups by numeric group ids (see permissionGroups.list — e.g. the "Business Owner" group grants ROLE_ADMIN). Grants access; does NOT create a login or email the person. Write.

Permission Groups

Tools

permissionGroups.getGet one permission group by id, including the ROLE_* strings it grants.
permissionGroups.listList the org's permission groups and the roles each one grants — e.g. the "Business Owner" group grants ROLE_ADMIN. Read this before people.setPermissionGroups: the roles in the response are the authority on what a group actually permits, so you never have to guess from its name.
permissionGroups.createCreate a permission group (name required; roles = list of ROLE_* strings it grants). Write.
permissionGroups.updateUpdate a permission group's name, description, or granted roles by id. Write.

Pipelines

Tools

pipelines.getGet one sales pipeline by id.
pipelines.listList sales pipelines. A pipeline owns an ordered set of stages — read them with stages.list filtered by pipeline.

Projects

Tools

projects.getGet one project by id — name, type, client, dates, description and price.
projects.listList projects. Filter by type (fixed-price | time-and-material | non-billable | internal), client.name, employee, name, or dateFrom/dateTo ranges. Use it to resolve the project id that tasks, work-time logging, budgets and contracts all take.
projects.createCreate a project (name + type required; type = fixed-price|time-and-material|non-billable|internal; optional dateFrom/dateTo, client, publicDescription, notes, priceNet). Write.
projects.updateUpdate a project by id (name, type, dates, description, etc.). Write.

Responsibilities

Tools

responsibilities.getGet one responsibility by id.
responsibilities.listList responsibilities inside a RACI group. Filter by responsibilityGroup. Responsibilities can nest via parent; people are assigned to them through responsibilityEmployees, not directly.
responsibilities.createCreate a responsibility inside a group (responsibilityGroup = group id or IRI, + name, required; optional description; optional parent = another responsibility IRI for nesting). Assign people to it via responsibilityEmployees.create. Write.
responsibilities.updateUpdate a responsibility by id (name, description, parent, responsibilityGroup = group id or IRI). Write.

Responsibility Employees

Tools

responsibilityEmployees.getGet one responsibility assignment by id.
responsibilityEmployees.listList who is assigned to which responsibility, and at what percentage. Filter by employee to read one person's entire RACI load across every group.
responsibilityEmployees.createAssign an employee to a responsibility (responsibility = responsibility id or IRI, employee = employee id or IRI, percentage 0-100, all required; optional targets and description). Write.
responsibilityEmployees.updateUpdate a responsibility assignment by id (percentage, targets, description). Write.
responsibilityEmployees.deleteRemove an employee's assignment from a responsibility by id. Write.

Responsibility Groups

Tools

responsibilityGroups.getGet one responsibility group by id.
responsibilityGroups.listList responsibility groups / RACI areas — the top-level "Odpowiedzialności" items, each with an accountable person. Individual responsibilities hang underneath them.
responsibilityGroups.createCreate a responsibility group / RACI area (name required; optional description and responsibleEmployee = the accountable person, given as a plain employee id like 6 (from people.list) or the /people/6 IRI). This is the top-level 'Odpowiedzialności' item. Add individual responsibilities under it via responsibilities.create. Write.
responsibilityGroups.updateUpdate a responsibility group by id (name, description, responsibleEmployee = employee id or IRI). Write.

Stages

Tools

stages.getGet one deal stage by id.
stages.listList deal stages, in order. Filter by pipeline. deals.create requires a stage id from here, and moving a deal between stages is what dealStageHistories records.

Suppliers

Tools

suppliers.listList suppliers/contractors — served from /contractors, so "supplier" and "contractor" are the same record. Filter by cyclic for recurring suppliers. Use it to resolve the supplier that a cost, a contract or an incoming invoice is filed against.
suppliers.createCreate a new supplier/contractor record (name, tinType, costGroup required). Write.
suppliers.updateUpdate a supplier/contractor's details (name, tax id, payment terms, etc.) by id. Write.

Tag Definitions

Tools

tagDefinitions.listList tag definitions — the tags that can be attached to records, each inside a tag group. tags.create takes a tagDefinition id from here plus the record to attach it to.
tagDefinitions.createCreate a tag definition (name, level, tagGroup required) within a tag group. Write.

Tag Groups

Tools

tagGroups.listList tag groups — the containers that organize tag definitions.
tagGroups.createCreate a tag group (name required) to organize related tag definitions. Write.

Task Comments

Tools

taskComments.listList comments on project tasks, oldest first. Filter by task to read one task's discussion.
taskComments.createAdd a comment to a project task (task id + content). Write.

Task Lists

Tools

taskLists.listList task lists — the board columns/sections tasks are filed into. Filter by project. tasks.create takes a list id from here.

Tasks

Tools

tasks.getGet one project task by id — title, project, status, list, assignees, dates and recurrence.
tasks.listList project tasks. Filter by project, list, status, assignees, isTemplate, or startAt/dueAt ranges. Recurring tasks expose recurrenceParent and recurrenceRule, so a generated occurrence can be traced back to the rule that produced it. To decide whether a task is DONE, compare its status against taskStatuses.list (isClosed) rather than matching on the status name.
tasks.createCreate a project task (title + project required; optional status, list, assignees, dueAt, priority). Write.
tasks.updateUpdate a project task by id — change status (incl. mark done), assignees, dueAt, title, etc. Write.

Task Statuses

Tools

taskStatuses.listList the project task statuses, in board order. isClosed marks the done states and isDefault the status a new task gets. Read this before interpreting a task's status — the names are org-configurable, so "Done" is not a reliable string to match on.

Tax Groups

Tools

taxGroups.listList tax groups. Use it to resolve the taxGroup id that taxRules.list filters on and that invoice rows carry.
taxGroups.createCreate a tax group (name + type required). Write.
taxGroups.updateUpdate a tax group's name or type by id. Write.

Tax Rules

Tools

taxRules.listList tax rules — the rates and the periods they apply to. Filter by taxGroup.
taxRules.createCreate a tax rule. Write.
taxRules.updateUpdate a tax rule by id. Write.

Transactions

Tools

transactions.listList bank transactions — the bank feed that incoming invoices are matched against. Filter by bankAccount, counterpartyRole, cost, ignored, hasDetectedProblems, an orderDate/execDate range, or amount.between. Note orderDate and execDate are different: a payment can be ordered in one month and execute in the next.
transactions.suggestionsRead Flowtly's proposals for one bank transaction — which counterparty, cost group or document it should be filed against. The mirror image of incomingInvoices.suggestions, from the money side.

Work Times

Tools

workTimes.getGet a single work-time entry by id — date, minutes, project, notes and the employee it belongs to.
workTimes.listList work-time (logged hours) entries. Filter by date range (date.after / date.before, YYYY-MM-DD) and optionally by employee or project; page with cursor. Each row carries employeeId/employeeName and projectId/projectName, so this is how you export all logged hours for a period. IMPORTANT: org-wide results require ROLE_WORKING_HOURS_VIEWER. Without it the backend does NOT error — it silently returns only the connected user's own entries, so an "everyone's hours" export can come back containing one person and look perfectly fine. If every row belongs to one employee and you did not filter by employee, the response carries a scopeWarning saying so — surface it to the user rather than presenting the result as org-wide.
workTimes.logLog a work-time entry for the connected Flowtly user (date, durationMinutes, project, notes). Write.

Tags

Tools

tags.createAttach a tag definition to a record (tagDefinition + relationName + relationId; e.g. relationName "counterparty" to tag a supplier/vendor). Write.

Client Contacts

Tools

clientContacts.createCreate a contact person for a client (client, type, name, email required). Write.

Counterparty Bank Accounts

Tools

counterpartyBankAccounts.createAttach a bank account to a counterparty (counterparty + accountNumber). Write.

Organization Icon

Tools

organizationIcon.uploadUpload/replace the organization's icon/favicon (base64 image + contentType + filename). Read the current one via configs.get organization-icon-url. Write.