Zyflow MCP tools at a glance
Zyflow runs one MCP server at one URL for every app. It has 32 tools, 5 prompts and 3 resources, speaks Streamable HTTP, and accepts an OAuth access token or an API key as a Bearer token.
| Item | Value |
|---|---|
| Server | name zyflow · title “Zyflow CRM” · version 1.0.0 · icon https://zyflowcrm.com/icon-192.png (192x192 PNG) |
| Server URL | https://app.zyflowcrm.com/mcp, the same for everyone |
| Transport | Streamable HTTP, stateless, plain JSON responses, one JSON-RPC message per POST |
| Spec versions | 2026-07-28; 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05 |
| Authentication | Authorization: Bearer with an OAuth access token (zfo_…) or an API key (zf_live_…) |
| Scopes | crm:read (every read tool) and crm:write (adds the 19 write tools) |
| Tools | 32: 13 read and 19 write. 5 carry destructiveHint. openWorldHint is false on all |
| Idempotency keys | On 13 write tools, kept 24 hours |
| Prompts | 5 |
| Resources | 3: two fixed URIs and one template |
| Request size | Up to 16 MB per request; files up to 10 MB through save_file |
| Billing | Your AI’s calls are never billed3 |
| This page as Markdown | /docs/mcp.md |
Limits by plan (record limit, history window, storage) are on the limits page.
How every tool works
These rules apply to all 32 tools: what a result looks like, how undo works, how customers are found, how retries behave, how dates and amounts are read, and how stored text is handled. The tool entries below don’t repeat them.
Results
Every result starts with one plain sentence saying what happened. Most tools follow it with compact JSON: names next to ids, money formatted, dates resolved and empty fields dropped. structuredContent carries the same content, sentence included, for MCP apps that read only that. Every write that changes something returns a change_id, and passing it to undo reverts everything that call did.
There are four shapes:
- Sentence plus JSON (every write tool, and upload_link). The text item is the sentence, a blank line, then the JSON. structuredContent is the JSON with the sentence in summary.
- Lists (list_records, get_history, list_files and list_documents). A count line, then one line per row with its id. structuredContent holds the rows as objects, plus next_cursor when there are more.
- A document as text (get_customer on a single match, fetch, search, get_briefing and describe_workspace). The text is the answer itself, and structuredContent carries it as text beside the key fields. search and fetch put JSON in the text, in the shape OpenAI documents for search and fetch tools.
- An image (get_file on an image up to 5 MB). The details come as text and the picture as an image item, with no structuredContent, so your app reads content.
create_document, update_document and get_document also add a resource_link item for the PDF.
A dry run, a repeat Zyflow ignored, or a save with nothing new returns no change_id. A value Zyflow can’t use, such as an invalid phone number or an unreadable date, is skipped rather than failing the call: the rest of the write goes through, and the sentence ends with a Note saying what was skipped and why.
Change IDs and undo
Each write tool call is one change batch, and its id starts with bat_. Write results return it as change_id, and get_history and the “What your AI did” part of get_briefing list it too. Every change in History carries the app’s name, such as “Added by Claude via MCP”.
Passing a change_id to undo reverts the whole call: created records are removed, edits go back, and deletes and merges are restored, folders with their contents. Fields edited again since are kept, and the result says so. Undoing the last invoice issued gives its number back. Settings changes, such as pipelines, custom fields and tag renames, are skipped with a reason. An undo is itself a change with its own change_id, so undoing it redoes the change.1
How far back undo reaches depends on the plan’s history window (see limits → /docs/limits). A change older than that fails with history_limit. Each write tool call runs in one database transaction, so it happens in full or not at all.
Finding the customer
Tools take customers the way people say them: a name, “Sam at Halden”, a phone, an email or an id. When one record clearly matches, the tool goes ahead. When more than one could, nothing is written and the candidates come back with what tells them apart, so your AI can ask which one you mean.4
Say a workspace has Sam Ortiz at Halden and Sam Lee at Fieldhouse, and your AI passes “Sam”:
- save_contact, save_company and save_deal return a normal result with status “ambiguous” and the candidates. Call again with id to update one, or with create_new: true to add someone new.
- get_customer returns status “ambiguous”, or “not_found” with the closest records, also as a normal result.
- remember, log_activity, save_task, update_dues, and the file and document tools that take a customer, deal or link return an error result with code ambiguous_reference, and its text lists the candidates. When nothing matches, the code is unknown_customer or unknown_record, with the closest records named.
In each case the next call passes the id (ct_… for Sam Ortiz) and goes through.
Retries, previews and versions
Of the 19 write tools, 13 accept an optional idempotency_key. Zyflow keeps each key for 24 hours. A retry with the same key and arguments returns the first result, marked “(Replayed: this key was already applied.)”. The same key with different arguments, or on a different tool, is refused with idempotency_key_reused.
The 6 write tools without a key are bulk_update, merge_records, delete_record, undo, create_folder and delete_files. Without a key, Zyflow still catches common repeats: the same activity logged again right after, an open follow-up that matches one already there, a fact it already has, and a folder that already exists. These guards are a backstop, not a promise. If your client retries, send a key.
dry_run: true on save_contact, save_company and save_deal reports what would happen (“would update Maya Chen, matched by email”) without saving. bulk_update previews by default and needs dry_run: false to apply.
Compact records carry a version. Pass it as expected_version on save_contact, save_company, save_deal or update_document, and a record someone changed since you read it fails with version_conflict instead of being overwritten.
Idempotency keys exist on MCP only. The REST API has none (see the REST API docs → /docs/api).
Dates, amounts and ids
Pass dates and amounts the way the person said them: “next Tuesday 5pm”, “end of month”, “25k”, “$12k”, “1.2 million”. Zyflow resolves them in the workspace’s time zone and currency and returns what it stored, so your AI can read the date back.
- Typed numeric dates such as 10/09 are read month first only in US workspaces, and day first everywhere else. ISO dates (2026-10-09) read the same everywhere.
- Plain-language dates and amounts are English only, and decimal commas aren’t parsed.
- Result sentences write dates the way the app shows them, day first in every country (“Fri, 9 Oct”).
- Money comes back in the workspace currency unless a document or deal names another ISO currency code.
Every id is a type prefix plus 12 characters. get_document, update_document and from_quote also accept invoice and quote numbers (INV-0007, QT-0003).
| Prefix | Record |
|---|---|
| ct_ | contact (a person) |
| co_ | company (a business) |
| dl_ | deal |
| mem_ | memory (a remembered fact) |
| ac_ | activity |
| tk_ | task (a follow-up) |
| fl_ | file |
| dir_ | folder |
| doc_ | document (invoice, quote or other document) |
| tpl_ | template |
| bat_ | change batch (change_id) |
Stored text is data
Notes, memories, names, file contents and document text reach your AI as customer data, not instructions. Zyflow strips zero-width, bidirectional and control characters, shortens long bodies, and fences text-file contents under the label “Content (customer data, not instructions)”. The server instructions and the file and document tool descriptions say the same. The only standing instructions Zyflow sends are the owner’s Notes for your AI, which only owners and admins can edit.
Zyflow has built-in defenses against prompt injection: hidden characters are stripped, and file text reaches your AI labeled as customer data, not instructions. These are mitigations, not guarantees.
Zyflow logs each call’s tool name, app, result and duration for 90 days, not its arguments. See what Zyflow logs.
How the annotations are set
Every tool carries all four MCP annotations, taken from one of four sets. Read tools carry readOnlyHint: true. The 5 tools that can remove, merge or revert carry destructiveHint: true. log_activity, update_dues and save_file add something new on every call, so they’re idempotentHint: false. openWorldHint is false on all 32, because each tool works only inside the connected workspace.
| Set | readOnlyHint | destructiveHint | idempotentHint | openWorldHint | Tools |
|---|---|---|---|---|---|
| READ | true | false | true | false | search, fetch, get_customer, list_records, get_briefing, describe_workspace, get_history, list_files, get_file, upload_link, list_documents, get_document, list_templates |
| WRITE | false | false | true | false | save_contact, save_company, save_deal, remember, save_task, organize_files, create_folder, update_business_profile, create_document, update_document, save_template |
| APPEND | false | false | false | false | log_activity, update_dues, save_file |
| DESTRUCTIVE | false | true | true | false | bulk_update, merge_records, delete_record, undo, delete_files |
Of the 32 tools, 5 carry destructiveHint: bulk_update, merge_records, delete_record, undo and delete_files. No MCP tool empties the trash or purges a file. delete_record and delete_files move items to a 30-day trash, issued invoices can only be voided, and bulk_update previews first and stops at 200 records.
Annotations are hints. Whether your MCP app asks you before a destructive call is up to the app.
Why the save tools aren’t marked destructive
save_contact, save_company and save_deal update the fields you pass and never delete a record. Emails, phones and tags are added to what’s there, not replaced. A clash between details returns “ambiguous” and writes nothing. Each call is one change, with the old values kept in History, and undo reverts it.1 expected_version stops a save from overwriting an edit made after your AI read the record.
Why undo is destructive
undo can remove records that the call it reverts had created, so it carries destructiveHint like the other tools that remove things.
Why every write result includes change_id
undo needs it. With the change_id in the result, your AI can reverse its own last action in the same conversation without another lookup, and the same id is what History and get_history show beside the change.
One caveat on idempotentHint
create_document is marked idempotentHint: true, but calling it twice without an idempotency_key makes two numbered documents. update_document with payment adds the amount each time it’s called. Send an idempotency_key with both when your client retries.
Find and read
These 7 read tools find customers, list records, brief the day, describe the workspace and show what changed, and none of them changes anything.
Every tool entry has the same parts:
[tool_name] · [title]
Badge: Read or Write, then the four annotation values
Parameters: the count and the required ones
Description and parameter table: generated from the server
Results, Errors and Example: written below
Under each group's last tool: Back to the tool list
search · Search the CRM
Badge: Read · READ · Parameters: 1, required: query
Results: Text whose content is JSON, {“results”: [...]}, with up to 10 hits. Each hit has id, title, url, type, subtitle and snippet. structuredContent holds the same object. Files and documents aren’t searched here; use list_files and list_documents.
Errors: none of its own.
Example: {“query”: “northwind”}
fetch · Read a record
Badge: Read · READ · Parameters: 1, required: id
Results: Text whose content is JSON: id, title, text, url and metadata. For a contact, company or deal, text is the full brief in Markdown. For a memory, activity or task, it’s a short snippet. structuredContent holds the same object.
Errors: bad_id (the value isn’t a Zyflow id) · not_found.
Example: {“id”: “ct_…”} (Maya Chen’s id, from search)
get_customer · Get everything about a customer
Badge: Read · READ · Parameters: 2, required: customer
Results: On one match, the brief as text, ending with an “Open in Zyflow” link. structuredContent: status “found”, id, type, name, url, detail and text. Several matches return status “ambiguous” with the candidates, and none returns “not_found” with the closest records. Both are normal results, not errors.
Errors: none of its own.
Example: {“customer”: “Maya at Northwind”}
list_records · List and filter records
Badge: Read · READ · Parameters: 32, required: type
Results: A count line (“[total] contacts match; showing [n]”), then one line per record with its id. structuredContent: type, total, next_cursor and items. Pass next_cursor back as cursor for the next page. A company filter that matches several businesses returns status “ambiguous” with the candidates.
Errors: unknown_member (owner isn’t a member) · not_found (no company matches) · bad_cursor.
Example (“customers I haven’t spoken to in a month”): {“type”: “contacts”, “lifecycle”: [“customer”], “inactive_days”: 30, “sort”: “least_recent”}
get_briefing · Daily briefing
Badge: Read · READ · Parameters: 1, required: none
Results: The briefing as text, in the order the Today page uses: Do today, Money, What your AI did (with change_ids), Pipeline and Gone quiet. structuredContent: today, timezone, currency, focus, text and the sections as data.
Errors: none of its own.
Example: {“focus”: “today”}
describe_workspace · Describe this workspace
Badge: Read · READ · Parameters: 0
Results: One sentence with the workspace name, today’s date, time zone, currency and date format, then the workspace as JSON. When the owner has written Notes for your AI, the sentence tells your AI to follow them.
Errors: none of its own.
Example: {}
get_history · Show recent changes
Badge: Read · READ · Parameters: 4, required: none
Results: “[n] change batches:”, then one line per batch: the day, who made it (marked “(AI)” for MCP), a summary, whether it was undone or can’t be undone, and its change_id. structuredContent: batches (change_id, at, by, via, summary, undoable, undoneAt) and has_more. With no since, it covers the last 30 days.
Errors: none of its own.
Example: {“agent”: “Claude”, “limit”: 10}
Save customers and deals
These 3 tools add or update people, businesses and deals, and each looks for the record you already have before it creates one.
save_contact · Add or update a person
Badge: Write · WRITE · Parameters: 27, required: none (pass at least a name, phone, email or id)
Results: A sentence saying what happened and how the record was matched (“matched by email”), with any company it linked or created. JSON: action (created, updated, unchanged, or a dry-run result), contact (with its version), company, possibleDuplicates, warnings and change_id. Similar names come back as “Possible duplicates (not merged)”. When the details could fit several people: status “ambiguous”, the candidates, and nothing written.
Errors: nothing_to_save · unknown_member · version_conflict · record_limit · idempotency_key_reused.
Example (a preview first): {“name”: “Maya Chen”, “company”: “Northwind”, “emails”: [“maya@northwind.example”], “lifecycle”: “customer”, “dry_run”: true}
save_company · Add or update a business
Badge: Write · WRITE · Parameters: 27, required: none
Results: The same shape as save_contact, with company in place of contact.
Errors: nothing_to_save · unknown_member · version_conflict · record_limit · idempotency_key_reused.
Example: {“name”: “Kestrel”, “website”: “https://kestrel.example”, “lifecycle”: “prospect”, “city”: “Denver”, “country”: “US”}
save_deal · Add or update a deal
Badge: Write · WRITE · Parameters: 21, required: none
Results: A sentence with the deal, its stage and amount. JSON: action, deal (with its version), the linked company and contact, warnings and change_id. Without an id, an open deal with the same title for the same business is updated instead of duplicated.
Errors: unknown_member · version_conflict · record_limit · idempotency_key_reused.
Example: {“title”: “Brand refresh”, “company”: “Kestrel”, “stage”: “Proposal”, “amount”: “$12k”, “expected_close”: “end of month”}
Memory, activity, follow-ups and money
These 4 tools record what’s true, what happened, what’s next and what’s owed, each on the customer the person named.
remember · Remember facts about a customer
Badge: Write · WRITE · Parameters: 4, required: customer, facts
Results: “Remembered [n] new facts about [customer] ([k] already known).”, then one line per fact with its category and mem_ id. JSON: memories (action, content, category, id) and a change_id when something new was saved. A fact passed with supersedes archives the old one.
Errors: no_facts · ambiguous_reference · unknown_customer · idempotency_key_reused.
Example: {“customer”: “Maya at Northwind”, “facts”: [{“content”: “Prefers email. No calls before 11am.”, “category”: “preference”, “pinned”: true}]}
log_activity · Log a call, meeting, message or note
Badge: Write · APPEND · Parameters: 8, required: customer, summary
Results: “Logged a [kind] with [customer] on [day] ([ac_ id]).”, plus a “Follow-up:” line when follow_up was passed. JSON: activity_id, follow_up and change_id. The call and its follow-up are one change. The same entry sent again right after is reported as already logged, with no change_id.
Errors: ambiguous_reference · unknown_customer · idempotency_key_reused.
Example (with two Sams, this first call returns ambiguous_reference listing Sam Ortiz at Halden and Sam Lee at Fieldhouse, and writes nothing): {“customer”: “Sam”, “kind”: “call”, “summary”: “Wants the revised proposal by Friday.”, “follow_up”: {“title”: “Send revised proposal”, “due”: “friday”}}
Then, once you say Halden, the same call with “customer”: “ct_…” (Sam Ortiz’s id) saves both and reads back the resolved due date.
save_task · Add, reschedule or complete a follow-up
Badge: Write · WRITE · Parameters: 11, required: none (a new task needs a title)
Results: “Added follow-up: [title] ([tk_ id])” with the resolved due date, or “That follow-up already exists”, or “Updated:” for a change by id. JSON: action, task and change_id.
Errors: title_required · not_found · unknown_member · ambiguous_reference · unknown_customer · idempotency_key_reused.
Example: {“title”: “Call Maya about the renewal”, “customer”: “Maya at Northwind”, “due”: “next tuesday 5pm”}
update_dues · Track money a customer owes
Badge: Write · APPEND · Parameters: 10, required: customer
Results: “[customer] now owes [amount], promised by [day] (was [amount]).”, then the collection reminder it created, updated or completed, and a warning when a business passes its credit limit. JSON: customer, outstanding, outstandingDue, currency, reminder, payment_logged and change_id. add and received change the balance on every call, which is why this tool is APPEND.
Errors: nothing_to_save · ambiguous_reference · unknown_customer · idempotency_key_reused.
Example: {“customer”: “Northwind”, “add”: 4800, “due”: “2026-10-15”, “note”: “Workshop invoice”}
Zyflow records payments; it doesn’t collect them. There are no card payment links.
Bulk, merge, delete and undo
This group holds 4 of the 5 destructive tools, and each is one change that undo can reverse.
bulk_update · Change many records at once
Badge: Write · DESTRUCTIVE · Parameters: 11, required: type
Results: By default a dry run: “Dry run: [n] [type] match ([names], …). Nothing changed yet; run again with dry_run: false to apply.” Applied: “Changed [n] of [m] [type]”, with any failures and the change_id. JSON: matched, changed, unchanged, failed (per record; one failure doesn’t stop the rest), sample and change_id.
Errors: no_target (no ids or filter) · bad_request (more than 200 records) · unknown_member.
Example (“tag everyone in Austin for the meetup”, previewed first): {“type”: “contacts”, “filter”: {“city”: “Austin”}, “add_tags”: [“austin-meetup”]}
merge_records · Merge duplicate records
Badge: Write · DESTRUCTIVE · Parameters: 3, required: type, keep_id, merge_ids
Results: “Merged [n] records into [id]”, with what moved, and the change_id. JSON: kept, merged, moved and change_id. Up to 10 duplicates per call. Files and documents linked to a duplicate don’t move, and deals can’t be merged.
Errors: bad_request (no duplicate, or more than 10) · not_found.
Example: {“type”: “contact”, “keep_id”: “ct_…”, “merge_ids”: [“ct_…”]}
delete_record · Delete a record
Badge: Write · DESTRUCTIVE · Parameters: 1, required: id
Results: “Deleted [name] ([id]). Undo with change_id [bat_…] if this was a mistake.” JSON: deleted and change_id. Contacts, companies, deals, activities, memories and tasks go to the 30-day trash. Draft documents and the workspace’s own templates are deleted, and undo brings them back.
Errors: bad_id · not_found.
Example: {“id”: “tk_…”}
undo · Undo a change
Badge: Write · DESTRUCTIVE · Parameters: 1, required: change_id
Results: “Undid [n] changes, skipped [k].”, then one line per change (“Reverted: …” or “Skipped …” with the reason), then “To redo, undo change_id [bat_…].” JSON: undone (a count), skipped (with reasons) and the undo’s own change_id.
Errors: not_found (no such batch in this workspace) · already_undone · history_limit.
Example: {“change_id”: “bat_…”}
Files
These 7 tools browse, read, store and tidy the business’s files drive. Through Zyflow, your AI can see images up to 5 MB and text files up to 1 MB. It can’t read the text inside PDFs, Word or Excel files.
list_files · List files and folders
Badge: Read · READ · Parameters: 5, required: none
Results: “[n] folders and [m] files [where]:”, one line per folder and file, then “Storage: [used] of [limit] used”, with the trash share and the largest file allowed. structuredContent: summary, folder, folders, files and storage.
Errors: folder_not_found · ambiguous_reference · unknown_record (for linked_to).
Example: {“linked_to”: “Northwind”}
get_file · Read a file
Badge: Read · READ · Parameters: 1, required: file
Results: A details block (name, kind, size, folder, linked records, who added it and an “Open in Zyflow” link), then the image, the fenced text or a download link, by the size and type rules in the description. An image result has no structuredContent. A file in the trash is still found, and marked.
Errors: not_found.
Example: {“file”: “call-notes-maya.md”}
upload_link · Link to upload a file
Badge: Read · READ · Parameters: 2, required: none
Results: “Give the user this link to upload the file into [folder], linked to [customer]: [url]”. The person needs to be signed in. JSON: url, folder and link. A folder or customer it can’t find is dropped with a Note rather than failing. It counts as a read tool because the person does the upload.
Errors: ambiguous_reference.
Example: {“folder”: “Clients/Northwind”, “link_to”: “Northwind”}
save_file · Save a file
Badge: Write · APPEND · Parameters: 10, required: name
Results: “Saved [file]. Open it: [url]”, plus any folders it created. JSON: file and change_id. A name already taken in the folder gets “ (2)”, so every call adds a file, which is why this tool is APPEND.
Errors: file_too_large · storage_full · ambiguous_reference · unknown_record · idempotency_key_reused.
Example: {“name”: “call-notes-maya.md”, “text”: “Call with Maya Chen: wants the revised quote by Friday.”, “folder”: “Clients/Northwind”, “company”: “Northwind”}
organize_files · Move, rename, describe or link files
Badge: Write · WRITE · Parameters: 7, required: files
Results: A sentence, one line per file changed, then “Undo with change_id [bat_…].” JSON: changed and change_id. Up to 50 files per call; rename works on one file at a time.
Errors: rename_one · not_found · ambiguous_reference · idempotency_key_reused.
Example: {“files”: [“fl_…”, “fl_…”], “move_to”: “Clients/Kestrel/2026”, “link_to”: “Kestrel”}
create_folder · Create a folder
Badge: Write · WRITE · Parameters: 1, required: path
Results: “Created folder [path] ([dir_ id])”, or “Folder [path] already exists; nothing changed.”, with an “Open it” link. JSON: folder, created and a change_id only when something was created. Paths nest up to 10 levels.
Errors: path_required.
Example: {“path”: “Clients/Kestrel/2026”}
delete_files · Move files to the trash
Badge: Write · DESTRUCTIVE · Parameters: 1, required: files
Results: “Moved [what] to the trash: [names].”, with the change_id that brings them back. JSON: trashed and change_id. Up to 50 items per call.
Errors: not_found.
Example: {“files”: [“old-logo.png”]}
Business profile
A single tool saves the details of the business itself, which documents print and every AI app receives.
update_business_profile · Update the business profile
Badge: Write · WRITE · Parameters: 18, required: none
Results: “Updated the business profile:”, one line per field changed, then “Undo with change_id [bat_…].” JSON: changed and change_id. emails and phones replace the whole list, and ai_notes replaces the notes, so pass the current notes with the new line.
Errors: insufficient_role (the person who connected is a member, not an owner or admin; the message tells your AI to ask the owner or an admin) · idempotency_key_reused.
Example (adding one line to Notes for your AI): {“ai_notes”: “<the current notes>\nQuote prices before tax.”}
Every member, and every member’s AI, can see and edit every customer. There are no per-person permissions yet.
Invoices, quotes and documents
These 6 tools read and make invoices, quotes and other documents from templates, and every one of them returns documents_off while documents are switched off in Settings.
list_documents · List invoices, quotes and documents
Badge: Read · READ · Parameters: 8, required: none
Results: “[n] invoices for [customer]:”, one line per document (number, customer, total, what’s still due, status, dates and id), then, for invoices, the totals unpaid, overdue and paid in the last 30 days. structuredContent: documents, next_cursor and totals.
Errors: ambiguous_reference · unknown_record · invalid_date · documents_off.
Example: {“kind”: “invoice”, “status”: [“overdue”]}
get_document · Read an invoice, quote or document
Badge: Read · READ · Parameters: 1, required: document
Results: The document as text (who it’s for, dates, lines, totals, what was paid, status, notes and template field values), a PDF link that works without signing in for 7 days, and an “Open in Zyflow” link, plus a resource_link item for the PDF. structuredContent adds the lines and the document’s version. A PDF that’s missing or failed before is made again here.
Errors: not_found · documents_off.
Example: {“document”: “INV-0007”}
list_templates · List document templates
Badge: Read · READ · Parameters: 2, required: none
Results: The 5 built-ins (Clean, Bold, Classic, Letter, Offer letter) and the workspace’s own, each with the fields it asks for. With template: that template’s full Liquid HTML, its fields and the guide to variables and filters. Built-ins can’t be changed; save an adapted copy with save_template.
Errors: not_found · documents_off.
Example: {“kind”: “invoice”}
create_document · Make an invoice, quote or document
Badge: Write · WRITE · Parameters: 22, required: kind
Results: A one-line summary, “PDF (no sign-in needed, valid for 7 days): [link]” and “Open in Zyflow: [url]”, plus a resource_link item for the PDF. JSON: document (id, number, kind, status, customer, currency, total, balance_due, dates, template, url, pdf and version), warnings and change_id. If the PDF can’t be made, the document is still saved and get_document tries the PDF again. Repeat calls without an idempotency_key make a second numbered document.
Zyflow works out totals, tax and numbering. Unit prices come from what you or your AI enter; there’s no price list.
Errors: invalid_kind (from_quote needs kind “invoice”) · invalid_status · ambiguous_reference · unknown_record · documents_off · idempotency_key_reused.
Example (“invoice Northwind for 3 workshop days at $1,200, plus 10% tax”): {“kind”: “invoice”, “customer”: “Northwind”, “items”: [{“name”: “Workshop day”, “quantity”: 3, “unit_price”: 1200, “tax_rate”: 10}], “idempotency_key”: “northwind-workshops-oct”}
update_document · Change an invoice, quote or document
Badge: Write · WRITE · Parameters: 23, required: document
Results: The same shape as create_document, with the PDF made again. A payment is added to what was paid, and the invoice is marked paid once it’s paid in full. status “void” is how an issued invoice or quote is canceled.
Errors: version_conflict · invalid_transition (for example, paying an invoice that’s already paid in full) · invalid_payment · items_required · documents_off · idempotency_key_reused.
Example (“Northwind paid $2,000 against INV-0007”): {“document”: “INV-0007”, “payment”: 2000}
save_template · Create or change a document template
Badge: Write · WRITE · Parameters: 11, required: none (a new template needs a name)
The description is the longest of any tool: its first paragraph shows, and the template guide (variables, filters and rules) sits under “Template guide”.
Results: “Created template “[name]” ([tpl_ id]) for invoices and quotes”, the preview link, and the create_document call that uses it. JSON: template (id, name, kinds, fields, version, default_for), preview, warnings and change_id. make_default from a member saves the template and adds a Note that it isn’t the default.
Errors: template_invalid · template_name_taken · name_required · html_required · documents_off · idempotency_key_reused.
Example (copy the Clean design under a new name and make it the default): {“name”: “Our invoice”, “based_on”: “Clean”, “kinds”: [“invoice”, “quote”], “make_default”: true}
Prompts
There are 5 prompts: daily_briefing, prepare_meeting, log_conversation, draft_follow_up and find_duplicates. They appear only in MCP apps that support prompts, often as slash commands. draft_follow_up only drafts, and log_conversation has your AI show you the list before anything is saved.
Each prompt is a message your app sends to your AI on your behalf. Your AI does the reading, sorting and writing; Zyflow answers the tool calls it makes.
| Name | Title | Arguments | What it asks your AI to do |
|---|---|---|---|
| daily_briefing | Plan my day | none | Call get_briefing, then give a short plan in the order of its Do today list, in under 12 lines, then the money to chase and the deals that need a push, and offer to draft the messages |
| prepare_meeting | Prepare for a meeting | customer (required) | Call get_customer with detail “full”, then cover who they are, what’s known about them, open deals, follow-ups, money owed, the last three interactions and three talking points |
| log_conversation | Save a conversation to the CRM | conversation (required), customer | Sort the pasted text into people and businesses, facts, a summary, follow-ups and money owed, show you that list and ask you to confirm, then save it with the matching tools |
| draft_follow_up | Draft a follow-up message | customer (required), channel (whatsapp, email or sms; WhatsApp by default) | Call get_customer, then draft a short message that picks up from the last interaction, in the customer’s language and tone if those are on record, without sending anything |
| find_duplicates | Find duplicate customers | none | Use list_records and search to find the same phone or email, or very similar names at the same company, show the pairs side by side, and merge only the ones you confirm |
Zyflow sends nothing to your customers. Your AI drafts; you press send.
Resources
There are 3 resources. zyflow://guide gives the data model and good habits (Markdown). zyflow://workspace gives pipelines, stages, custom fields, tags, members and valid values (JSON). zyflow://record/{id} gives the full brief for one contact, company or deal.
| URI | Name and title | Type | Returns |
|---|---|---|---|
| zyflow://guide | guide · How to use Zyflow | text/markdown | The record types and their id prefixes, the habits the server asks AI apps to follow, and example calls |
| zyflow://workspace | workspace · Workspace schema | application/json | The same workspace data describe_workspace returns |
| zyflow://record/{id} | record · Customer record | text/markdown | The full brief for a ct_, co_ or dl_ id; any other id gets a one-line text/plain reply |
zyflow://record/{id} is a resource template. It can’t be listed or subscribed to: read one record at a time by its id.
Server instructions
When an AI app connects, Zyflow sends server instructions: a fixed guide to its tools, then an “About this business” paragraph and the owner’s Notes for your AI, labeled as standing instructions. The paragraph and the notes together are capped at about 1,500 characters.
The fixed part
Generated from the server: the instructions text, word for word, with a Copy button.
What Zyflow adds for each workspace
After a blank line, Zyflow appends this, filled in from the workspace and its business profile:
About this business (from its Zyflow profile): <business name> (<industry>). <what the business does, up to 500 characters> It is in <country>: amounts are in <currency>, times in <time zone>, and typed dates are read <day first (DD/MM), or month first (MM/DD) in US workspaces>.
The business owner’s standing instructions for AI apps (follow them unless the user asks otherwise):
- <Notes for your AI>
- The second and third lines appear only when the owner has written Notes for your AI. Only owners and admins can edit them.
- Notes can be up to 2,000 characters. When the paragraph and notes would pass about 1,500, the notes are cut and end with “(the rest: describe_workspace → business.aiNotes)”. describe_workspace always returns the full notes.
- Zyflow caches the text for up to a minute per workspace and drops it the moment the profile changes. MCP apps read instructions when they connect, so a session that’s already open may keep the notes it started with.
- If the profile can’t be read, the fixed part goes out on its own.5
Protocol versions and transport
Zyflow’s MCP server uses Streamable HTTP at https://app.zyflowcrm.com/mcp. It’s stateless and answers in plain JSON, one JSON-RPC message per POST; batches are refused. It supports the 2026-07-28 MCP spec and 2025-era clients (2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05). Requests can be up to 16 MB.
Every request needs Authorization: Bearer with an OAuth access token from Zyflow’s sign-in or a Zyflow API key. Without one, the server answers 401 with a WWW-Authenticate header whose resource_metadata points to https://app.zyflowcrm.com/.well-known/oauth-protected-resource/mcp. That’s how MCP apps start sign-in.
- 2025-era clients are served through the MCP SDK’s stateless legacy fallback, at the same URL.
- Authentication happens before the protocol layer, and cookies are never accepted.
- The server answers cross-origin requests from any origin without credentials, so a browser-based client can call it with a bearer token.
- Responses are plain JSON, not server-sent events.
- OAuth 2.1 details (discovery, registration, PKCE, token lifetimes) are on the MCP server overview. API keys and the local bridge are covered in the guide to other MCP apps.
Not supported
The server doesn’t use sampling, elicitation, progress notifications or resource subscriptions, and it doesn’t accept JSON-RPC batches. One connection reaches one workspace, and scopes are all-or-nothing: crm:read or crm:read plus crm:write, with no per-tool or per-record limits.
Errors
Errors come at two levels. Problems with the request are HTTP statuses: 401, 403 insufficient_scope, 429 with Retry-After, and 400 for batches. Problems inside a tool come back as a tools/call result with isError and a code, such as version_conflict, idempotency_key_reused or record_limit.
HTTP level (the tool never runs)
Each of these answers with a JSON-RPC error body whose id is null.
| Status | When | What comes back |
|---|---|---|
| 401 | No Authorization: Bearer header | WWW-Authenticate: Bearer realm=”zyflow”, resource_metadata=”https://app.zyflowcrm.com/.well-known/oauth-protected-resource/mcp”, scope=”crm:read crm:write”. Error -32001, “Authentication required. Connect Zyflow to this app first.” |
| 401 invalid_token | The token or key is invalid, expired or revoked | The same header plus error=”invalid_token”. Error -32001, “The access token is invalid, expired or revoked. Reconnect Zyflow.” |
| 403 insufficient_scope | A read-only connection or key calls a write tool | WWW-Authenticate with error=”insufficient_scope” and scope=”crm:read crm:write”, so the app can ask for more. Error -32001, “This connection is read-only. Reconnect with write access to change records.” |
| 429 | Too many calls in a short burst | A Retry-After header in seconds. Error -32000, “Too many requests. Slow down and retry shortly.” |
| 400 | A JSON-RPC batch (an array) | Error -32600, “JSON-RPC batches are not supported; send one request per POST.” |
A request body over 16 MB is refused before it reaches the MCP server.
A connection or API key with only crm:read still lists all 32 tools. Any call to a write tool is refused with HTTP 403 insufficient_scope before the tool runs.
The 429 is a short burst limit that stops runaway scripts. Your AI’s calls are never billed.3
Tool level (a normal tools/call response)
The JSON-RPC call succeeds and the result has isError: true. Its one text item is a plain sentence your AI can pass on, then a numbered Candidates list when there are some, then “(code: …)” on the last line.
| Code | Tools | What it means and what to do |
|---|---|---|
| version_conflict | save_contact, save_company, save_deal, update_document | The record changed after your AI read it. Read it again, then retry with the new version |
| idempotency_key_reused | The 13 tools with idempotency_key | The key was used in the last 24 hours for different arguments or another tool. Use a new key for a new write |
| record_limit | Saves that create a contact, company or deal | The workspace is at its plan’s record limit, and nothing was written. See limits |
| ambiguous_reference | Tools that take a customer, deal or link without saving one | Several records could match, and the candidates are listed. Ask the person, then retry with the id |
| unknown_customer, unknown_record | The same tools | Nothing matches; the closest records are named |
| insufficient_scope | Every write tool | A second scope check inside the tool. Over MCP, the HTTP 403 comes first |
| insufficient_role | update_business_profile | Only an owner or admin can change the business profile. The message tells your AI to ask one |
| already_undone | undo | That change was undone already. To redo it, undo the undo’s change_id |
| history_limit | undo | The change is older than the plan’s history window. See limits |
| not_found | Tools that take an id, a file, a document or a change_id | Nothing with that reference in this workspace |
| documents_off | The 6 document tools | Documents are switched off. An owner or admin turns them on in Settings > Documents |
| storage_full | save_file | The workspace has used its storage, trash included |
| file_too_large | save_file | Over 10 MB through an AI connection. Give the person a link from upload_link instead |
| bad_id | fetch, delete_record | The value isn’t a Zyflow id |
| nothing_to_save | save_contact, update_dues | No field to change was passed |
| template_invalid | save_template | The HTML failed the check. Each problem has a line and column, and nothing was saved |
| (no code) | Any tool | “Zyflow hit an unexpected error. Try again; if it keeps failing, the data was not changed.” |
Arguments that don’t match a tool’s input schema are rejected by the MCP SDK before the tool runs.
Warnings aren’t errors. A value Zyflow skipped is named in a Note at the end of the sentence and in warnings, and the rest of the call goes through.
Other codes for single bad values (invalid_date, unknown_member, rename_one, path_required and the rest) are listed under each tool.
Common questions
Is this the same tool list my MCP app sees?
Yes. The page is generated from the server’s own tools/list on every release, and the server version and date are printed at the top. Titles, descriptions, annotations and input schemas are word for word what your app receives. If your app shows something different, compare the version it reports at connect with the one above, and tell us.
Why don’t save_contact and the other save tools carry destructiveHint?
They update the fields you pass and never delete a record. Emails, phones and tags are added, not replaced, and a clash writes nothing and returns “ambiguous”. Each call is one change with the old values kept in History, and undo reverts it.1
How do I undo something an AI app did through MCP?
Pass the change_id from the write result to undo, or find it with get_history. You can also press Undo next to the change in Zyflow’s History. Undo works within your plan’s history window, and settings changes can’t be undone.1
What happens when my client retries a write?
With the same idempotency_key and arguments within 24 hours, Zyflow returns the first result instead of writing again. Without a key, common repeats such as the same activity or a follow-up already there are caught, but not every one. bulk_update, merge_records, delete_record, undo, create_folder and delete_files take no key.
Why can’t I see Zyflow’s prompts in my app?
Prompts appear only in MCP apps that support them, usually as slash commands. Tools and resources work either way, and each prompt is a short message you can type yourself.
Which tools work on a read-only connection?
All 13 read tools. A connection or key with only crm:read still lists every tool, but a write call gets HTTP 403 insufficient_scope before it runs. The consent screen grants what the app asks for, which is read and write for Claude and ChatGPT2 today. For read-only access, use a read-only API key or an MCP client that asks only for crm:read; see the guide to other MCP apps.