MCP overview
The ziptie.ai API is available as a Model Context Protocol (MCP) server at:
https://api.ziptie.ai/mcpThis lets AI assistants (Claude, Cursor, VS Code with Copilot) call ziptie.ai API endpoints as tools within a conversation - no custom code required.
What is MCP?
MCP (Model Context Protocol) is an open standard for connecting AI assistants to external data sources and tools. When you configure an MCP server, the AI can call your tools, read the results, and use them to answer questions or take actions on your behalf.
The ziptie.ai MCP server exposes every public /v1/ endpoint as a named tool
(for example, list_prompts, get_dashboard_overview, create_tag). The
tool manifest is generated from the same OpenAPI spec that drives the REST API,
so they are always in sync.
Transport
The ziptie.ai MCP server uses Streamable HTTP transport: a single
POST https://api.ziptie.ai/mcp endpoint that accepts JSON-RPC 2.0 envelopes
and returns JSON responses. Most current MCP clients support this transport
natively.
Authentication
MCP requests use the same zt_live_... API tokens as the REST API, and the
same entitlement rules. The workspace must have the paid API & MCP access add-on
active - without it every call, including initialize, is rejected with
API_ADDON_INACTIVE, and trial workspaces cannot enable the add-on. A token is
either a project token, which reaches the single project it was created
under, or a workspace token, which reaches every project in its workspace. Every
project_id argument must be a project the token reaches, or the tool call
returns PROJECT_MISMATCH. Call list_projects to find the ids. See
authentication for the two kinds.
Pass the token in the Authorization header:
Authorization: Bearer zt_live_<your-token>Each MCP client has its own way to configure this header - see the per-client setup guides below.
Rotating and revoking tokens
A project token is created under Project Settings > API & MCP access with one expiry choice - 30 days, 90 days, 365 days, or never. A workspace token is created by a workspace Owner or Admin under Workspace Settings > API & MCP access with 30, 90 or 365 days and no never option, and it works only while the person who created it is still an Owner or Admin. Either way the secret is shown exactly once, at creation. There is no rotate-in-place. To replace a token you create a second one, update the client config, then revoke the first.
Revocation takes effect on the next request: every call re-checks the token
against the database, so a revoked token fails with TOKEN_REVOKED from that
moment on. Over /mcp that arrives as JSON-RPC -32001 at HTTP 200 with
data.code set to TOKEN_REVOKED, not as a 401 - see
errors. Revoked and expired tokens stay in the list with
their status so you can see what was retired.
A project can hold 10 active project tokens and a workspace 10 active workspace
tokens, where active means neither revoked nor past its expiry. The eleventh create returns 409
TOKEN_LIMIT_REACHED; revoking one frees the slot.
This matters for the shared config files the client guides describe. Claude
Code's .mcp.json is meant to be committed, and VS Code's .vscode/mcp.json
is shared with collaborators. If a token reaches either of those, or any
dotfiles repo, treat it as public: revoke it and issue a replacement. A token's
scope and project are fixed at creation and cannot be narrowed afterwards.
Scope ladder
The token's scope applies to MCP tool calls exactly as it does to REST calls:
read- can call all read (list_*,get_*) toolswrite- can also callcreate_*andupdate_*toolsadmin- can also calldelete_*tools
A token carries exactly one of the three levels, picked at creation.
Choose the lowest level that does the job, because the assistant decides when
to call a tool, not you. An admin token lets it call delete_prompt,
delete_tag and delete_url against live tracked data; those are real
deletes, with no confirmation step and no undo. A write token lets it call
create_prompt and create_accept, each of which consumes a monitored-prompt
slot from the subscription's allowance and fails with PROMPT_LIMIT_REACHED or
INSUFFICIENT_CREDITS once that allowance is used up. Only monitored prompts
hold a slot, so a create_prompt with monitoring_status false consumes
nothing and is never refused for allowance. Use a read token for
exploration and reporting, and mint a separate write or admin token only
for the work that needs it.
Tools
tools/list returns 77 tools, one per public /v1/ operation, generated from
the same OpenAPI spec as the REST API. Every one of them except list_projects
takes project_id, and that value has to be a project the token reaches.
| Area | Tools |
|---|---|
| Project | list_projects, get_project |
| Prompts | list_prompts, get_prompt, get_prompt_ugc_evidence, create_prompt, create_prompts_bulk, update_prompt, delete_prompt |
| Prompt performance | list_prompt_performance, get_prompts_summary |
| Prompt checks | list_checks, list_check_answers, list_prompt_checks, get_prompt_check, get_prompt_check_latest |
| Prompt suggestions | get_prompt_suggestions, create_accept, create_dismiss |
| Tags | list_tags, create_tag, update_tag, delete_tag |
| URLs | list_urls, get_url, create_url, update_url, delete_url |
| Dashboard | get_dashboard_overview, get_llm_trends, get_ugc_impact, get_ugc_platform_sources |
| Brand aliases | list_brand_aliases |
| Competitors | list_competitors, get_competitors_citations, get_competitors_mentions, get_competitors_sources, get_competitors_trends, get_competitor_brand_facts, get_serp, get_competitors_bing_organic |
| Competitor drill-downs | list_competitor_mention_prompts, list_competitor_citation_prompts, list_competitor_citation_urls, list_competitor_source_prompts, list_competitor_serp_prompts, list_competitor_serp_urls, list_competitor_bing_organic_prompts, list_competitor_bing_organic_urls |
| Competitor tags | list_competitor_tags, create_competitor_tag, update_competitor_tag, delete_competitor_tag, list_competitor_tag_members, create_competitor_tag_members, delete_competitor_tag_member |
| UGC Impact | get_topic_clusters, get_topics, get_sources, get_ugc_attributes, get_ugc_voices |
| Search Console | get_gsc_trends, list_gsc_pages, list_gsc_queries |
| Website | list_website_pages, get_website_page |
| Brand facts | list_brand_facts, create_brand_fact, update_brand_fact, delete_brand_fact |
| Answer accuracy | get_answer_accuracy_summary, list_answer_accuracy_problems, get_answer_accuracy_problem, list_answer_accuracy_problem_answers, dismiss_answer_accuracy_problem, undismiss_answer_accuracy_problem |
| Limits | get_limits |
55 of those are read, 14 are write, and 8 are admin: the 6 deletes and the 2 brand-fact writes.
list_prompt_performance and get_prompts_summary read the app's Prompts
table and its summary card: each prompt's AI Score, its change over the window
and every engine's answer at its latest check. The eight drill-down tools list
the prompts and pages behind one row of a competitor ranking, over the same
window, engines and prompt filters as that row, so their counts add up to the
row's count. Each read returns applied, the window and the engines the
numbers cover, so an assistant can say exactly what it measured.
list_projects is the one to call first: it returns the projects the token
reaches, each with its id, name, country, language, check cadence, the engines
enabled for that project and freshness, one page at a time (limit, offset, and sort as
name:asc, name:desc, created_at:asc or created_at:desc). Pass an id to
get_project next. Every other tool is scoped to a project but describes none
of it, so get_project is what lets an assistant say which domains and brands
are tracked, which engines are being checked, and whether the numbers it is
about to read are fresh. Re-list rather than caching ids: the list follows the
token's access at the moment of the call.
With a workspace token the flow is the same for every client project:
list_projects, then any other tool once per project_id from the list. A
project the token does not reach answers -32002 PROJECT_MISMATCH, and if the
reach check cannot be made the call answers -32000 SERVICE_UNAVAILABLE,
which is safe to retry. Each tools/call costs one unit whichever project it
names, against the workspace token's own per-minute limit (120) and the shared
workspace limits.
What is deliberately absent matters as much as what is there. There is no
project create, update or delete, so an assistant can read a project's
configuration but cannot change or destroy it. There are no workspace, billing, subscription, notification, export or
content-brief tools. There is no way to trigger a check on demand: checks run
on the project's own schedule, and these tools only read what those runs
produced. There is also no get_tag - fetch a single tag by passing its name
to the q filter on list_tags. list_competitors reads the tracked-competitor
roster, the brands and domains carrying at least one competitor tag, and the
competitor tag tools change it. Hiding a brand or domain stays an in-app action;
the competitor rankings leave the hidden rows out by default and return them
with filter=hidden.
Rate limits
Each tools/call costs one unit in each of the per-token (60/min) and
per-workspace (600/min, 50000/day) buckets, the same as one /v1/ request. The
unit is charged when the call reaches /mcp; the /v1/ request the tool runs
internally is not charged again. A call refused before it dispatches, such as
an unknown tool or a scope refusal, still costs its unit, as a REST 4xx does.
initialize and tools/list are exempt. See
rate limits.
Errors
A failed tool call still returns HTTP 200. The failure lives inside the
JSON-RPC envelope, as an error object whose data field carries the
underlying API envelope:
{
"jsonrpc": "2.0",
"id": 3,
"error": {
"code": -32002,
"message": "Token scope does not cover 'admin'",
"data": {"code": "INSUFFICIENT_SCOPE", "error": "Token scope does not cover 'admin'"}
}
}| JSON-RPC code | Meaning |
|---|---|
-32001 |
Unauthorized - token missing, malformed, expired, or revoked |
-32002 |
Forbidden - scope too low, or project_id is a project the token does not reach |
-32003 |
Rate limited |
-32004 |
Payment required - API & MCP access add-on off, a payment not confirmed, a closed workspace, a read-only subscription (SUBSCRIPTION_READ_ONLY) or a plan cap reached (PROMPT_LIMIT_REACHED, INSUFFICIENT_CREDITS) |
-32005 |
Not found, including PROJECT_NOT_FOUND |
-32006 |
Maintenance - a declared maintenance window is in progress. Retry after data.retry_after_seconds; nothing is broken |
-32009 |
Conflict - a duplicate, idempotency-key reuse, an already-resolved suggestion, or a Search Console connection that must be reconnected (GSC_REAUTH_REQUIRED) |
-32602 |
Invalid params - missing required argument, or a rejected filter value |
-32601 |
Unknown tool name or unknown JSON-RPC method |
-32000 |
Server error - an upstream fault, an unreachable dependency or an internal error |
Branch on error.data.code, not on the JSON-RPC number and not on the HTTP
status. Several distinct API codes share one JSON-RPC number: -32004, for
one, means "the workspace owner has something to pay for or renew", and
data.code says what. A code with no row of its own in the
mapping table follows the
HTTP status it carries on /v1/ (a 404 is -32005, a 409 is -32009,
and so on), so a new code still arrives in the right family.
Every failure reaches you as a JSON-RPC error. Four checks run in front of the
router and can refuse a call before the tool executes - maintenance, then
authentication, then entitlement, then rate limiting - and all four reply with
HTTP 200 and a JSON-RPC error member, exactly as the router does. There is
no condition on this endpoint that answers with a 401, 402, 403, 429 or 503, so
a client that branches on the HTTP status will not see any of them.
The four are the only errors you can receive without your tool having run:
| Refused by | error.code |
data.code |
|---|---|---|
| Maintenance window | -32006 |
MAINTENANCE_MODE |
| Authentication | -32001 |
MISSING_AUTH_HEADER, INVALID_AUTH_FORMAT, TOKEN_INVALID, TOKEN_REVOKED, TOKEN_EXPIRED |
| Entitlement | -32004 |
PAYMENT_REQUIRED, API_ADDON_INACTIVE, ENTITLEMENT_DENIED |
| Rate limiting | -32003 |
RATE_LIMITED |
They are listed in the order they run, and the first one to refuse is the one
you hear from. A call with no token that would also have been rate limited comes
back -32001, not -32003; during a maintenance window it comes back -32006
whatever else is wrong with it.
Authentication and entitlement
A missing, malformed, revoked, expired or unknown token is -32001:
{
"jsonrpc": "2.0",
"id": 3,
"error": {
"code": -32001,
"message": "API token has been revoked",
"data": {"code": "TOKEN_REVOKED", "error": "API token has been revoked"}
}
}An entitlement refusal - the API & MCP access add-on switched off, a first payment
not yet confirmed, a closed workspace, or API access not enabled for the
workspace - has the same shape at -32004. Read data.code to tell them
apart: -32001 means fix the token, -32004 means the workspace owner has
something to resolve in billing settings, and neither is worth retrying until
they do.
A workspace whose paid period has ended but which is still open is read-only
rather than refused. Every read tool keeps working, and every write tool
(create_*, update_*, delete_*) returns -32004 with data.code
SUBSCRIPTION_READ_ONLY. That refusal comes from the tool's own /v1/ call,
after the four checks below, so initialize, tools/list and the read tools
are unaffected. get_project reports the state ahead of a write as
write_access: false. A workspace in dunning keeps full access while it is
inside its paid period.
If the subscription check itself cannot be made, the call is refused with
-32000 and data.code SERVICE_UNAVAILABLE. That answer is not cached, so
retry after a short wait.
Neither carries a header, and neither adds keys to data beyond code and
error. id is echoed from your request, or null when the body carried none.
Rate limits
A tools/call that exceeds any bucket is refused with -32003, at HTTP
200, before the tool runs:
{
"jsonrpc": "2.0",
"id": 3,
"error": {
"code": -32003,
"message": "Rate limit exceeded",
"data": {
"code": "RATE_LIMITED",
"error": "Rate limit exceeded",
"retry_after_seconds": 37
}
}
}retry_after_secondsis always present and is the seconds until the current minute window resets. TheRetry-Afterheader carries the same value for a non-agent probe.idis echoed from your request, ornullwhen the body carried none.- The
X-RateLimit-*headers are a/v1/feature and are not attached here. - The buckets and the
initialize/tools/listexemptions are described in rate limits.
Maintenance windows
While a maintenance window is declared, every /mcp call is refused. The reply
is still HTTP 200 with a JSON-RPC error, deliberately: a transport-level 503
handed to an agent tends to become a confident wrong answer for its user, while a
typed error with a retry hint becomes a correct one.
{
"jsonrpc": "2.0",
"id": 3,
"error": {
"code": -32006,
"message": "Service is under maintenance",
"data": {
"code": "MAINTENANCE_MODE",
"error": "Service is under maintenance",
"reason": "Scheduled database upgrade",
"ends_at": "2026-08-07T03:00:00+00:00",
"retry_after_seconds": 1800
}
}
}retry_after_secondsis always present. It is the seconds until the declared end of the window, or300when no end is declared, and also300when the declared end has already passed and the window is still open. Back off on this value rather than onends_at.reasonis the operator's note andends_atis the declared end; either can benull.idis echoed from your request. It isnullif the body was not parseable JSON-RPC, since the window is decided before the envelope is validated.- The
Retry-AfterandX-Maintenance-Mode: 1headers carry the same signal for a non-agent probe.
This is the case the "branch on error.data.code" rule is worth most: an agent
that reads the JSON-RPC number as a generic server error reports a broken API to
its user, when the correct behaviour is to wait and retry once.
Client setup guides
Supported methods
| JSON-RPC method | Description |
|---|---|
initialize |
Handshake; returns server info and capability flags |
tools/list |
Returns the full tool manifest |
tools/call |
Calls one tool by name with arguments |
Nothing else is implemented. initialize advertises capabilities as
{"tools": {}} and nothing more, so resources, prompts, notifications,
sampling, roots, completion, and logging are all absent, as is SSE. If your
client expects to read MCP resources or fetch server-side prompt templates, it
will find none here.