← API reference

Error codes

All error responses use a consistent JSON envelope:

{"error": "Human-readable description", "code": "MACHINE_READABLE_CODE"}

Use the code field for programmatic handling - the error string may change without notice. The code field is stable across API versions.

Full code table

The HTTP column is the status you get on /v1/. On /mcp it does not apply: every error there is an HTTP 200 carrying a JSON-RPC error member, including the authentication, entitlement and rate-limit refusals decided before your tool runs. See MCP JSON-RPC error mappings for the translation.

Code HTTP Context Fix
MISSING_AUTH_HEADER 401 No Authorization header on a /v1/* or /mcp request. Over MCP this and the four codes below it arrive as JSON-RPC -32001 at HTTP 200, not as a 401 Add Authorization: Bearer zt_live_... header
INVALID_AUTH_FORMAT 401 Header present but wrong format (not Bearer zt_live_...) Verify the header value and token prefix
TOKEN_INVALID 401 Token not found or fails verification Check that the token was copied correctly; create a new one if needed
TOKEN_REVOKED 401 Token exists but was explicitly revoked, or it is a workspace token whose creator is no longer an Owner or Admin of the workspace Create a new token, or ask an Owner or Admin to create one
TOKEN_EXPIRED 401 Token's expires_at is in the past Create a new token
PROJECT_MISMATCH 403 The project in the URL is not reachable by this token: another project for a project token, another workspace for a workspace token. A project that does not exist answers the same way, never 404 Call GET /v1/projects to list the project ids the token reaches, or use a token that reaches this project
ENTITLEMENT_DENIED 403 Workspace is not entitled to call this endpoint. Over MCP this arrives as JSON-RPC -32004 at HTTP 200, not as a 403 Contact support
INSUFFICIENT_SCOPE 403 Token scope too low for this operation (e.g. read token on a write endpoint) Re-create the token with write or admin scope
SUBSCRIPTION_READ_ONLY 403 The workspace is read-only: its subscription's paid period has ended, so every write (POST, PATCH, DELETE and the matching MCP tools) is refused while every read keeps working. GET /v1/projects/{project_id} reports it ahead of a write as write_access: false. A subscription that is past due, cancelling at period end, or awaiting payment confirmation but still inside its paid period is not affected and keeps full write access. Over MCP this arrives as JSON-RPC -32004 Renew the subscription in workspace billing settings. Retrying will not help until then
RATE_LIMITED 429 Any rate-limit bucket exceeded. Over MCP this arrives as JSON-RPC -32003 at HTTP 200, not as a 429 (see below) Wait for the Retry-After seconds, then retry
API_ADDON_INACTIVE 403 The API & MCP access add-on is not active for this workspace, the workspace is on a trial, or it has no subscription at all. Over MCP this arrives as JSON-RPC -32004 at HTTP 200, not as a 403 Enable API & MCP access in workspace billing settings
PAID_PLAN_REQUIRED 402 The endpoint reads a paid-plan feature and the workspace's plan cannot be confirmed as paid. The features are website/pages, website/pages/{page_id}, brand-facts and answer-accuracy. Two cases do not get this code: a workspace on a trial is refused earlier with API_ADDON_INACTIVE (403), and a workspace whose paid plan has lapsed keeps reading the history it holds. Over MCP this arrives as JSON-RPC -32004 at HTTP 200, not as a 402 Subscribe to a paid plan
PAYMENT_REQUIRED 402 The workspace's first payment is not yet confirmed, or its subscription has ended and the workspace is closed. A workspace in dunning keeps full access while it is inside its paid period, and one whose paid period has ended but which is still open is read-only (SUBSCRIPTION_READ_ONLY) rather than refused. Over MCP this arrives as JSON-RPC -32004 at HTTP 200, not as a 402 Resolve the billing issue in workspace settings
VALIDATION_ERROR 422 Request parameters failed validation, including a date that is not a YYYY-MM-DD day or a date_from after today (UTC) Read the error field for which parameter failed
DATE_RANGE_TOO_LARGE 422 date_to - date_from exceeds 365 days, or 3660 days on GET /v1/projects/{project_id}/prompts/{prompt_id} Narrow the date range
NOT_FOUND 404 Resource ID does not exist or belongs to a different project, including a tag or URL PATCH on an id the project does not hold (a DELETE of one answers 204), a website page_id that is not a page of the project, and a property on a Search Console read that names a property the project has no active connection for Verify the ID; cross-project IDs return 404 not 403
PROJECT_NOT_FOUND 404 The project no longer exists upstream. Distinct from NOT_FOUND, which is about a resource inside a project, and from PROJECT_MISMATCH, which is a 403 for asking about a project the token does not reach. Returned by GET /v1/projects/{project_id}. Over MCP this arrives as JSON-RPC -32005 Nothing to retry. The project was deleted. A project token for it is now useless, so create one for a different project; a workspace token keeps working for the workspace's other projects, which GET /v1/projects lists
PROMPT_ALREADY_EXISTS 409 The project already holds a prompt whose text matches the one sent. The comparison trims, collapses internal whitespace runs and ignores case, so Acme CRM and acme crm are one prompt, and it applies within the project only Use the existing prompt or update it instead. Nothing was created and no allowance was spent. existing_id names the match where the lookup reaches it, and a null there still means the text matched
TAG_ALREADY_EXISTS 409 A tag with the same name already exists in this project, compared ignoring case. Returned by a create and by a rename onto another tag's name. existing_id names the tag that holds it Use the existing tag (existing_id) or choose another name. Nothing was created or changed
BRAND_FACT_LIMIT_REACHED 409 The project already holds its 15 brand facts, paused ones included. Over MCP this arrives as JSON-RPC -32009 at HTTP 200 Delete or edit an existing fact, then retry
BRAND_FACT_INVALID 422 The statement is empty, longer than 200 characters, or not a single claim about your own business. Over MCP this arrives as JSON-RPC -32602 at HTTP 200 Rewrite the statement as one short claim and retry
PROBLEM_NOT_DISMISSABLE 409 Dismiss was called on an answer-accuracy problem that is not open. Over MCP this arrives as JSON-RPC -32009 at HTTP 200 Read the problem; only an open problem can be dismissed
PROBLEM_NOT_DISMISSED 409 Undismiss was called on a problem that is not dismissed. Over MCP this arrives as JSON-RPC -32009 at HTTP 200 Read the problem; there is nothing to undo
FEATURE_DISABLED 404 The answer-accuracy and brand-facts operations answer this while answer accuracy is not switched on. Over MCP this arrives as JSON-RPC -32005 at HTTP 200 Not caused by the request; retry later
COMPETITOR_TAG_ALREADY_EXISTS 409 A competitor tag with the same name already exists in this project. Names are compared ignoring case, and a competitor tag may share its name with a prompt tag Use the existing competitor tag (find it with GET /v1/projects/{project_id}/competitor-tags?q=) or choose another name
URL_ALREADY_EXISTS 409 The project already tracks this URL, compared as the exact string (so https://x.com/a and https://x.com/a/ are two URLs). Returned by a create and by a PATCH onto another URL's value. existing_id names the tracked URL Use the existing URL (existing_id). Nothing was created or changed
GSC_REAUTH_REQUIRED 409 The project's Google Search Console connection must be reconnected: Google revoked or expired its token. Returned by gsc/queries, gsc/pages and gsc/trends. A property that is not connected at all is not an error; those endpoints answer gsc_connected: false Reconnect the property in the ziptie.ai app. Retrying will not help until then
ALREADY_RESOLVED 409 Prompt suggestion has already been accepted or dismissed, or its text matches a prompt the project already tracks under the comparison described for PROMPT_ALREADY_EXISTS Idempotent retries: treat as success and stop replaying the same accept/dismiss call. In the matching-text case nothing was created or billed and the suggestion is left pending, so dismiss it to stop it being offered
INSUFFICIENT_CREDITS 402 The call would exceed the workspace's monitored-prompt slots for the project's check frequency (daily, weekly, or monthly). Returned by accepting a prompt suggestion, creating a prompt with monitoring_status: true, and resuming monitoring on a paused one, since each of those claims a slot. Creating a prompt with monitoring_status: false claims none and never returns this code. The workspace is entitled to write, it has simply used up the period's slots - contrast SUBSCRIPTION_READ_ONLY, which means it is not entitled at all. The body also carries check_frequency, cap, held and resets_at naming the exhausted bucket, its cap, the held count and when the window resets Free up slots by deleting or pausing monitored prompts, or raise the prompt counts on your subscription
PROMPT_LIMIT_REACHED 402 The workspace has reached its prompt cap for the cadence the project runs on. The allowance is bought per cadence (daily, weekly, monthly) and the bucket a create spends from is the project's own check_frequency_days, so free daily slots do nothing for a weekly project. Only monitored prompts count against it, and within a cadence it is counted across every project in the workspace. Creating a prompt with monitoring_status: false is not measured against it and never returns this code, whatever the workspace has left. The body also carries check_frequency, cap, held and resets_at when the project's cadence bucket resolved Pause or delete monitored prompts in any project of the workspace running on that cadence, or raise that cadence's prompt count on your subscription. Paused prompts are unlimited, so pausing is always available
TAG_LIMIT_REACHED 402 Reserved. No current subscription caps tags, so the API does not return this code today No action needed
URL_LIMIT_REACHED 402 Reserved. No current subscription caps URLs, so the API does not return this code today No action needed
IDEMPOTENCY_KEY_REUSED 409 Same Idempotency-Key submitted with a different request body Use a new key for a different request; resend the identical body to replay
INVALID_ENGINE_NAME 400 A value in the engines query parameter, or an engine named by the signal_filters, changes or leader_engine prompt filter, is not a recognised public engine key Use one of: chatgpt, google_aio, perplexity, google_ai_mode, copilot, bing_ai_overview, gemini
ENGINE_NOT_ENABLED 400 Every engine in the engines query parameter is a real engine, but none of them is enabled for the project: the workspace's subscription lacks it, or a workspace Owner or Admin switched it off for the project. Returned by every endpoint that filters on engines. A filter that names at least one enabled engine is not refused: the engines the project has not enabled are dropped, the response covers the rest, and applied.engines lists the ones it covers. On prompts/performance and prompts/summary it is also returned when a signal_filters rule, a changes token or leader_engine names an engine the project has not enabled, or when organic reads google without google_aio (or bing without bing_ai_overview) enabled for the project; the message names the engine Remove the engines the project does not have enabled, or omit engines; on the prompt reads, drop the filter that needs the engine. Retrying the same request will not help
SERVICE_UNAVAILABLE 503 A backing dependency is temporarily unreachable. That includes the subscription check every request makes, whose failure is not cached, and the competitor marks that the competitor rankings and the overview's top_competitors apply, which are never skipped to show hidden rows Retry after a short delay with backoff; if it persists, open a thread in the in-app Support Center or email [email protected]
MAINTENANCE_MODE 503 A declared maintenance window is in progress. Every /v1/* and /mcp request is refused for its duration; nothing is broken and no request is being served against a database that may be mid-restore Wait for the Retry-After seconds and retry. Retrying sooner will not succeed, and this is not worth reporting
UPSTREAM_ERROR 500 A backing service returned an unexpected or malformed response Retry once; if it persists, contact support with the request ID
INTERNAL_ERROR 500 Unhandled server-side error Retry once; if it persists, contact support with the request ID

Maintenance windows

MAINTENANCE_MODE is the one refusal that is deliberate rather than a fault, so it is worth handling apart from SERVICE_UNAVAILABLE. Its REST 503 and its MCP reply (which is an HTTP 200, see below) both carry two headers:

The REST body carries one key beyond the usual envelope:

{
  "error": "Service is under maintenance",
  "code": "MAINTENANCE_MODE",
  "maintenance": {
    "message": "Scheduled database upgrade",
    "ends_at": "2026-08-07T03:00:00+00:00"
  }
}

maintenance.message is the operator's note and maintenance.ends_at is the declared end; either can be null. Neither is a commitment - treat Retry-After as the value to back off on.

Health and reference endpoints keep serving during a window, so /health and /openapi.json are not a way to detect one. The 503 itself is the signal.

MCP JSON-RPC error mappings

When accessed via MCP (POST /mcp), HTTP errors are translated to JSON-RPC error codes:

JSON-RPC code Name API codes
-32001 Unauthorized MISSING_AUTH_HEADER, INVALID_AUTH_FORMAT, TOKEN_INVALID, TOKEN_REVOKED, TOKEN_EXPIRED, UNAUTHORIZED
-32002 Forbidden PROJECT_MISMATCH, INSUFFICIENT_SCOPE, FORBIDDEN
-32003 RateLimited RATE_LIMITED
-32004 PaymentRequired ENTITLEMENT_DENIED, API_ADDON_INACTIVE, PAYMENT_REQUIRED, PAID_PLAN_REQUIRED, SUBSCRIPTION_READ_ONLY, PROMPT_LIMIT_REACHED, INSUFFICIENT_CREDITS, TAG_LIMIT_REACHED, URL_LIMIT_REACHED
-32005 NotFound NOT_FOUND, PROJECT_NOT_FOUND, FEATURE_DISABLED
-32006 Maintenance MAINTENANCE_MODE
-32009 Conflict IDEMPOTENCY_KEY_REUSED, PROMPT_ALREADY_EXISTS, TAG_ALREADY_EXISTS, COMPETITOR_TAG_ALREADY_EXISTS, BRAND_FACT_LIMIT_REACHED, PROBLEM_NOT_DISMISSABLE, PROBLEM_NOT_DISMISSED, URL_ALREADY_EXISTS, ALREADY_RESOLVED, GSC_REAUTH_REQUIRED, CONFLICT, TOKEN_LIMIT_REACHED
-32602 Invalid params VALIDATION_ERROR, DATE_RANGE_TOO_LARGE, INVALID_ENGINE_NAME, ENGINE_NOT_ENABLED, BRAND_FACT_INVALID
-32000 ServerError SERVICE_UNAVAILABLE, UPSTREAM_ERROR, INTERNAL_ERROR, BAD_GATEWAY, GATEWAY_TIMEOUT
-32601 Method not found Unknown MCP tool name

Every MCP error is an HTTP 200 carrying a JSON-RPC error member. That includes every condition REST answers with a status code an HTTP client would normally branch on: an authentication failure, an entitlement refusal, a rate-limit breach and a maintenance window are all decided before the tool runs, and all four still arrive as JSON-RPC. Branch on the JSON-RPC error.code, or on data.code, rather than on the transport status - on /mcp there is no status to branch on.

The data field of every MCP error contains the original {"code": "...", "error": "..."} envelope. Two codes add keys to it so an agent can back off for the right length of time without parsing headers:

-32004 groups every refusal the workspace clears by paying: the add-on switched off, a payment not confirmed, a read-only subscription (SUBSCRIPTION_READ_ONLY, a 403 on /v1/) and a plan cap (the four cap codes, each a 402 on /v1/).

A code with no row in the table above follows the HTTP status it carries on /v1/: 400 and 422 arrive as -32602, 401 as -32001, 402 as -32004, 403 as -32002, 404 as -32005, 409 as -32009, 429 as -32003, and any other status as -32000 ServerError. data.code always carries the REST code, so branch on it rather than on the JSON-RPC code before retrying, since a plain retry will not clear a -32009 or a -32004.

TOKEN_LIMIT_REACHED is listed for completeness. It is answered when a token is created in the app, which no MCP tool does.

Getting help

status.ziptie.ai carries incident notices. It is maintained by hand, so the absence of a notice is not an automated all-clear. It is hosted off the platform it reports on, so it stays reachable through an outage large enough to take everything else down. The Terms of Service carry no contractual uptime commitment, so a 500 or 503 that survives a retry is worth reporting rather than waiting out, whatever the page says.

Responses from api.ziptie.ai carry an X-Request-ID header. Include it, the UTC timestamp, the endpoint and the code you received, then use either:

Neither channel publishes a response-time target, and there is no separate incident hotline.

A suspected security vulnerability goes to [email protected] instead, acknowledged within 5 business days. That address is also published at /.well-known/security.txt. Do not open a support thread for one: it queues behind billing questions and is read by the wrong person first.