Endpoint
GET and DELETE on this URL return 405.
Authentication
Send your API key as one of these headers. Both are accepted, and both are standard header names that MCP clients allow without special configuration:X-API-Keywins. If both headers are present, Flozy readsX-API-Keyand never looks atAuthorization. A staleX-API-Keyleft over from an earlier attempt will keep failing even after you add a correctAuthorizationheader.- The
Authorizationform only works with a Flozy key. The value must literally begin withBearer flz_. Anything else is treated as no key at all, which produces the “API key required” 401 rather than the “invalid key” 401.
Before you paste a key: one key means one agency
The Flozy External API has no per-user concept. An API key identifies an agency, and nothing else. Everything below follows from that, and none of it is configurable:- Every person using a connector configured with your key acts as the same principal, with the same scopes, over the same agency’s data. There is no per-user permission layer to fall back on.
- There is no per-user audit trail. The only usage attribution Flozy records is
last_used_aton the key itself, visible in Settings → API Keys. Ten people using the connector produce one timestamp on one key. - If the key holds
delete:leads, anyone who can talk to that connector can trash a lead — and trashing a lead cascades to its opportunities and tasks. Flozy cannot tell you who asked for it.
.mcp.json is scoped to everyone who can reach it. Choose the scopes accordingly — see
Choosing scopes — and prefer a separate, read-only key for anything shared.
You cannot change the scopes of an existing key. There is no edit operation: to change what a key can
do, create a new key with the scopes you want, re-point the connector at it, and revoke the old one.
(Rotating a key replaces the secret and keeps the scopes.)
Setup
Claude Code
--scope user to make the connector available in every project instead of just the current one.
The equivalent config block, if you prefer to write it by hand — .mcp.json in the project root, or
the user-level settings file:
.mcp.json written with --scope project is committed with your repository. Do not commit a key
this way unless the repository is private and everyone with access is meant to have the key’s
scopes — see the section above.
Cursor
Cursor reads MCP servers from~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one
project). The same shape works:
type key is needed — a server with a url and no command is treated as remote.
Claude Desktop — same beta as Claude.ai
Claude Desktop is not like Claude Code and Cursor here, despite running on your own machine. Itsclaude_desktop_config.json configures local (stdio) servers only and cannot point at this
endpoint. A remote server such as Flozy is added through Settings → Connectors → Add custom
connector, which is the same custom-connector mechanism claude.ai uses — including for the request
header that carries your key.
That header field is part of the same beta described below, so treat this path as unverified in
exactly the same way. If it is available to you, paste the endpoint URL and supply the key as an
X-API-Key or Authorization request header. If it is not, use Claude Code or Cursor on the same
machine — those work today and need no beta access.
Claude.ai and mobile — not yet verified
On claude.ai, a remote MCP server is added as a custom connector, and a fixed credential is attached to it with thestatic_headers feature — an org admin pastes the key once when adding
the connector, and it is sent as a request header on every call.
We have not verified this path. static_headers is a beta feature on Anthropic’s side, and we
have not yet confirmed it is available on the plans our customers use. The Flozy side needs nothing
built for it — the server already accepts both x-api-key and authorization, which are the
standard header names this feature allows — but “the server is ready” is not the same as “it works
for you today”, and we will not claim the second until we have tested it.
So, concretely: it is expected to work via static_headers, and it is not yet verified on our
side. If the option is not available when you try to add the connector — no place to set a header,
or the connector form only offers OAuth — then:
- Do not work around it by putting the key in a URL query string or in the connector’s name. Flozy does not read a key from anywhere but the two headers above, and neither of those places is a safe place for a credential.
- Use Claude Code or Cursor instead. Those two read the key from a config file you control, work today, and need no beta access. Claude Desktop does not help here — it goes through the same custom-connector beta.
- Tell us. Which plan you are on and what the connector form offered is exactly the information we need, and it is the thing blocking us from documenting this path properly.
Check the connection
You can call the endpoint directly. This lists the tools your key can use:initialize handshake is needed first — the endpoint is stateless.
The Accept header is not optional
If the Accept header does not list both, the transport rejects the request with 406 before it
reaches any Flozy code:
-H "Accept: ..." line above verbatim.
Tools
Nineteen tools. The Scope column is what your API key must hold for the tool to be available at all — if the scope is missing, the tool is not advertised to the agent and cannot be called. See Troubleshooting for what that looks like from the chat.
Three tools need a second scope for part of what they do, and in each case the extra scope is
checked inside Flozy rather than by the connector:
get_leadsilently omits thecontactskey withoutread:contacts. The call still succeeds.create_leadandupdate_leadfail entirely ifcustom_fieldsis sent withoutwrite:custom_fields— the lead is not created, and the other fields of an update are not applied. This is the one that surprises people; see Choosing scopes.
Choosing scopes
Scopes are the only access control on this connector. There is nothing else to configure and no per-user layer underneath, so the scope list on the key is the whole of your security posture. A workable default:- Start read-only.
read:leads,read:tasks,read:opportunities,read:contacts,read:pipelines,read:lead_statuses,read:custom_fields. An agent with these can answer any question about the CRM and change nothing. This is the right key for a shared workspace, and the right key to start with while you learn how the agent behaves. - Add
write:*one resource at a time, as you find yourself asking the agent to do something it cannot.write:tasksis usually the first and the safest — a wrong task is noise.write:leadsandwrite:opportunitieschange records your team reports on. - Withhold
delete:*by default. Deletes are the scopes to leave off unless you have a specific reason.delete:leadsin particular cascades to the lead’s opportunities and tasks, and on a shared key there is no record of who asked for it.
write:custom_fieldsis not optional if your workspace uses custom fields. The lead write routes only requirewrite:leads, but sending acustom_fieldsobject additionally requireswrite:custom_fields, and without it the whole write is rejected — not just the custom field part. Acreate_leadthat would have worked fine fails and creates nothing. If your leads carry custom fields, grantwrite:custom_fieldsalongsidewrite:leads; if you deliberately do not want the agent touching them, expect the agent to hit this and be ready to tell it to retry without custom fields.read:custom_fields,read:pipelinesandread:lead_statusesare cheap and high value. They are whatdescribe_workspacereads, anddescribe_workspaceis where the agent gets the pipeline stage ids, status names and custom field keys that every write depends on. Without them the agent is guessing, and a guessed lead status silently creates a new status in your workspace rather than failing.
Troubleshooting
A tool my agent says does not exist
Start here. A tool the agent cannot find is almost always a missing scope on your key, not a bug. Flozy only advertises the tools your key is scoped for. A key withoutdelete:leads is never told
delete_lead exists, and if something calls it by name anyway the server answers:
-32602; most clients show it to the model as a “tool not found” error.) That
message is honest about the outcome and misleading about the cause: the tool exists, your key just
cannot use it. The refusal itself is correct and nothing was executed — but you will not be told
which scope was missing, which is why this section exists.
How to fix it:
- Find the tool in the table above and read its Scope column.
- Check what your key actually holds, in Settings → API Keys — the Scopes column, or View details.
- If the scope is missing: create a new key with the scopes you want. There is no way to edit the scopes of an existing key.
- Update the header in your MCP client to the new key, restart or reconnect the client, and revoke the old key.
tools/list curl from
Check the connection. What comes back is exactly what the agent sees.
Two related cases:
tools/listcomes back empty ({"tools":[]}). The key holds none of the scopes any tool needs. In this one case atools/calldoes name the missing scope in its error, because there is no filtered tool list to hide behind.- The agent can see a tool but a specific field fails. That is a different problem — see
write:custom_fieldsunder Choosing scopes, and the validation errors below.
401 — invalid or revoked key
An HTTP401 with Flozy’s standard envelope, before any MCP processing:
- The header name is wrong, or the client is not sending it. Confirm with the
tools/listcurl — if curl works and the client does not, the problem is the client config. - The
Authorizationvalue does not start withBearer flz_. See Authentication; this produces the second message, not the first. - The key was revoked or rotated. Rotating issues a new secret; the old one stops working.
- The key expired. Keys can be created with an expiry date; an expired key behaves exactly like a revoked one. Check Status and the expiry in Settings → API Keys.
- You just revoked or rotated and it still works. Authentication results are cached for 60 seconds — the old key can keep authenticating for up to a minute. Wait it out before concluding the revoke failed.
401 fails the whole HTTP request, before any
MCP processing — so the client reports a broken connector rather than a tool error, and no tool runs
at all. If your agent reports that Flozy is unreachable rather than that a tool failed, check here
first.
403 — the input was rejected
A validation failure. Because your key’s scopes are checked before Flozy is called, a403 is
never a plain “missing scope on this tool” here — that case is the missing tool
above. A 403 means the input itself was rejected, and it is
the one error class where retrying with corrected input actually works.
You will not see an HTTP 403 at the MCP endpoint. It surfaces inside the tool result, with Flozy’s
validation message passed through verbatim and marked retryable, e.g.:
title, but
nothing in the text says title. An agent will normally guess correctly on a small input and can
guess wrong on a large one, so if you see it retrying and failing the same way, tell it which field
you think is at fault.
Agents generally handle these on their own — read the message, fix the field, call again. You need to
step in when the message points at something the agent cannot know: a pipeline stage that does not
exist in your workspace, a lead id from somewhere else, a custom field key you renamed. Telling the
agent to call describe_workspace first resolves most of them.
One case looks like a validation error but is really a scope problem: sending custom_fields
without write:custom_fields. It comes back on the same 403 path, but marked not retryable, so
the agent is told to stop rather than loop:
write:custom_fields on a new key, or tell
the agent to re-send the write without the fields that need it.
404 — not found
Also a tool result rather than an HTTP status. The record does not exist, or does not belong to your agency. Marked non-retryable, because the same id will not start existing. If the agent invented the id, point it atsearch_leads / search_tasks / search_opportunities to look the record up
properly.
405 — method not allowed
You sent aGET or DELETE. The endpoint is stateless and accepts POST only — there is no SSE
stream to open and no session to delete. If an MCP client is doing this, it is trying to negotiate a
stateful session; check that it is configured for Streamable HTTP rather than the older SSE
transport.
406 — the Accept header
Covered above: the request must accept both application/json and text/event-stream. If you
are seeing this from a real MCP client rather than from curl, the client is sending a non-standard
Accept header and that is worth reporting to us.
429 — rate limited
An HTTP429 with the standard envelope:
Rate limits
The MCP endpoint has its own budget, separate from the REST API’s:
Every response carries
X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (a Unix
timestamp). On a 429, wait until X-RateLimit-Reset and retry.
The ceiling is per key, not per person — so on a shared connector, every user in the workspace
draws from the same budget. It is set for agent traffic, which arrives in bursts: a single agent turn
commonly fans out 8–25 tool calls at once and then goes quiet for tens of seconds, and some tools
(describe_workspace, get_lead) read several records per call. If you are hitting it in normal
use, tell us — that is a signal the ceiling is wrong, not that you are doing something unusual.
Context cost
The other budget worth knowing about is your agent’s context window, and it binds long before the rate limit does:- Simply having the connector enabled costs north of 10,000 tokens per conversation — the tool list itself, sent before the agent does anything. (The tool descriptions alone are about 5,000; the rest is the input schema for every tool, and it grew when the task and deal filters landed.)
- A single full page of leads (
search_leadsatlimit: 100) is roughly 25,000 tokens.
search_leads by status, pipeline and
creation date; search_tasks by status, priority, lead and due date; search_opportunities by
stage, lead and close date) — and pagination.total answers “how many?” without returning a single
row.
Known limitations
Honest list. These are things the tools genuinely cannot do, so that you recognise them when an agent works around them badly. Lead search is narrower than it looks.search_leads filters by title substring, status,
pipeline and creation date, combined with AND. It cannot filter by:
- custom field values,
- owner or assignee,
- the lead’s
abouttext, - “leads with no status”.
pipeline_id means less than it sounds like. A lead is not attached to a pipeline in Flozy’s data
model. Filtering search_leads by pipeline_id actually means “has at least one opportunity in this
pipeline”. A lead that is plainly in a sales motion but has no opportunity record never matches,
no matter how obviously it belongs there. Making this mean something else would require a real
lead-to-pipeline relationship in the product, not a query change.
Task search cannot filter by assignee, and takes one status at a time. search_tasks filters by
text (title and description), status, priority, lead_id and a due_after / due_before date
range, combined with AND. It cannot filter by assignee or owner, by start date, or by parent task
and subtask relationships, and it sorts by creation date only — there is no sort by due date.
status and priority each take a single value. “Everything still open” is not one call: it is
one call per status you consider open, or one unfiltered call that you narrow afterwards. Expect an
agent asked for “all open work” either to make three calls or to quietly pick one status.
A task with no due date matches neither due_after nor due_before. The filter is a plain date
comparison, and a NULL due date fails both ends of it. So a due-date range silently excludes
every undated task — which reads like missing data rather than a filter doing its job. If the
answer looks short, ask for the same query without the date bounds.
Opportunity search filters by stage, not by pipeline. search_opportunities filters by text (the
parent lead’s title and the deal note), stage_id, lead_id, and an expected_close_after /
expected_close_before range. There is deliberately no pipeline_id filter — to ask “what is in
this pipeline?” the agent filters one stage at a time, using the stage ids describe_workspace lists
for that pipeline. This is the reverse of search_leads, which does take pipeline_id; the
asymmetry is real and intentional, and follows from the same data model described above. It cannot
filter by value, confidence, owner or creation date, and sorts by creation date only.
Date filters want YYYY-MM-DD exactly. All of created_after / created_before, due_after /
due_before, expected_close_after / expected_close_before take a plain calendar date. A date-time
is rejected rather than truncated, with a message saying so. Both bounds are inclusive — a
range of 2026-01-01 to 2026-01-31 includes both days.
Opportunity responses are ids, not names. search_opportunities and get_opportunity return
stage_id and lead_id and no names. The agent has to resolve them through describe_workspace and
search_leads before it can say anything readable.
Updating a lead’s about text replaces it wholesale, rather than merging. Sending new about
content through update_lead discards what was there.
Not exposed at all: sequences, campaigns, outreach and the inbox. This is deliberate — giving an
agent the ability to send email on your behalf is a separate decision with compliance weight, and it
is not part of this connector. Do not expect an agent to be able to email a lead through Flozy.
Lead, task and opportunity deletes are soft — the record goes to the workspace trash and a Flozy
admin can restore it through the UI. There is no restore tool, so the agent cannot undo its own
delete. Contact deletes are not recoverable.