Skip to main content

Overview

Every error response from the REST API (/api/mcp/*, /api/v1/*, and the Looker Studio connector) is shaped as application/problem+json — a machine-readable envelope that additively extends the API’s original { error: "..." } shape. Existing integrations that only read error keep working unmodified.
Some errors carry additional fields beyond this base shape (e.g. requiresUpgrade, retryable, current/limit on a quota error) — those are documented per-code below and are additive, never replacing the fields above.

Quick reference

Authentication & authorization

unauthorized

401. The request has no Authorization: Bearer sea_... header, the key doesn’t start with sea_, or the key is invalid, revoked, or expired. Fix: Check the header is Authorization: Bearer sea_xxxxx. Create or rotate the key under Manage Account → Integrations → API Keys.

sign_in_required

401. Returned by the public share endpoints — GET /api/visibility/shared/{token}, GET /api/chat/shared/{token}, and the .../verify routes on both — when the workspace that owns the shared report or conversation has its sharing policy set to “team only” and the request carries no signed-in session belonging to a member of that workspace. Fix: Sign in as a member of the workspace that owns the report or conversation, then retry.

forbidden

403. The authenticated key’s user doesn’t have access to the requested resource (distinct from a missing scope or a not-found — the resource exists but this key’s user isn’t a member, their role doesn’t allow the change, such as a workspace viewer starting an audit, or the project is inactive). Fix: Confirm the key’s owning user is a member of the workspace that owns the resource, with a role that can make the change.

missing_scope

403. The key doesn’t carry the scope or capability the endpoint requires. Read endpoints, and generating a report, require read; starting an audit or refreshing a sitemap requires write; the endpoints that change a project’s setup each need a capability. admin covers all of them. Fix: A key’s scopes can’t be changed after it’s created. Create a new key under Manage Account → Integrations → API Keys, with Allow write access on if it needs to make changes, which adds write and every capability. Then revoke the old key. See Write access.

plan_upgrade_required

403. The action is gated behind a plan the workspace isn’t on (white-label branding, report sharing, Shopping Analytics, Looker Studio integration, query fanout, …). Often carries requiresUpgrade: true. Fix: Upgrade the workspace’s plan under Settings → Billing.

security_policy_denied

403. The workspace that owns the requested project has API and MCP access switched off by a workspace-level security policy. This is an administered state, not a plan or scope problem: the key is valid, and it keeps working on any other workspace its owner belongs to — only requests that act on the restricted workspace’s projects are refused. Never carries requiresUpgrade. The same code is returned by the share-creating endpoints — including POST /api/mcp/projects/{projectId}/reports and the MCP server’s generate_report tool (as a tool error result with error.code), both of which publish a shareable report — when the workspace’s report sharing policy is switched off (“Report sharing is turned off for this workspace.”). A “team only” sharing policy does not refuse creation; it only limits who can open the link. Fix: Nothing on your side will clear it. The restriction is set by Searchable for the workspace at the customer’s request; contact the workspace’s administrator, or Searchable support, to have it lifted.

Not found & validation

not_found

404. The resource doesn’t exist, or the API key’s project binding doesn’t allow it to reach the requested project (a project-bound key can never see another project, even one its owning user can access in-app — see Scopes). Deliberately indistinguishable from “doesn’t exist” so existence never leaks to a caller who shouldn’t see it. Fix: Double-check the id in the URL. If the key is project-bound, confirm you’re calling it for its own project.

invalid_argument

400. A query parameter, path parameter, or request body failed validation (e.g. an out-of-range days, a malformed date, an invalid enum value, or a body that fails its schema). The response often carries details with the field-level breakdown. Fix: Check the parameter/body shape against the endpoint’s documented params.

prompt_gates_failed

422. At least one candidate sent to POST .../prompt-generation/import failed the import gates (no em/en dash, no semicolon, the market’s spelling, no near-duplicate), so the whole batch was refused and nothing was created. Carries validation (what each gate found) and outcomes (one row per candidate). Fix: Rewrite or drop the refused candidates (validation says what each gate found), then import again.

no_eligible_recipients

400. Returned by POST /api/reports/pdf with mode: "schedule" when the workspace’s sharing policy is “team only” and none of the requested recipients is the workspace owner or a member — a schedule nobody could ever receive is refused rather than created. Nothing is persisted: no schedule and no generated report. A partial list (at least one member) still creates the schedule and lists the non-members in dropped. Fix: Include at least one workspace member among the recipients, or ask the workspace administrator to relax the sharing policy.

invalid_country

400. The country filter value isn’t a recognized ISO code or name. Fix: Pass a valid ISO 3166-1 country code (e.g. US, GB) or locationId instead.

no_domain

400. The project has no verified domain — required before an audit or sitemap sync can run (the audited origin is always derived from the project’s own domain, never a caller-supplied URL). Fix: Set the project’s domain in the dashboard, then retry.

no_workspace

400 or 403. The project isn’t linked to a workspace, so plan/feature checks (Looker Studio, audits) have nothing to evaluate against. Fix: This is an account-configuration issue — contact support@searchable.com.

no_pages

400. POST /audits was called with no tracked pages and no explicit pageIds, or every id in pageIds didn’t match a page in this project. Fix: Track at least one page first, or pass valid pageIds for this project.

bulk_limit_exceeded

400. The requested page count for a single POST /audits call exceeds the workspace’s plan limit. Carries bulkLimitExceeded: true, requested, and maxAllowed. Fix: Pass a smaller pageIds list, split the audit into multiple calls, or upgrade the plan.

Quota & runnability

quota_exceeded

403. A monthly or concurrent usage quota (audits, reports, prompts, …) is exhausted. Carries quotaExceeded: true, current, and limit. Adding prompts checks the whole batch first: a batch that would go over the prompt allowance adds nothing. Fix: Wait for the quota to reset (monthly quotas), reduce concurrent usage, or upgrade the plan. For prompts, archive some tracked prompts or add fewer.

project_not_runnable

403. The project is paused or archived. Background work (audits, sitemap sync, report generation) requires an active, non-archived project. Fix: Resume the project in the dashboard before retrying.

pitch_not_supported

403. The endpoint isn’t available for pitch (scoped, pre-conversion) projects — opportunities, audits, sitemap sync, and archiving or reactivating a project are regular-project-only features. Fix: This endpoint only supports regular (non-pitch) projects.

Conflicts & project state

last_domain

400. Archiving this project would leave its workspace with no active project, and a workspace keeps at least one. Fix: Reactivate or create another project in the workspace first, then archive this one.

stale_revision

409. The prompt or project changed since the revision you sent (expectedRevision, or If-Match when editing a prompt). Carries currentRevision. Fix: Read the current state, check the change still makes sense, then retry with currentRevision.

conflict

409. The item already exists: a competitor with that domain or name (when adding one with source: "manual"), or a topic with that name. A topic conflict carries the existing topic’s id as existingTopicId. Fix: Use the existing competitor or topic, or choose a different name.

duplicate_domain

409. The workspace already has a project on that domain. Fix: Use the existing project, or create the new one in a different workspace.

INACTIVE_PITCH_PROMPT_MUTATION

409. The project is a pitch that is no longer active (declined, expired, archived or accepted), so it’s a frozen review snapshot: its prompts, topics, competitors and brand profile can’t be changed. The body is { error, code }, not the problem format above. Fix: Make the change on an active project instead.

Rate limiting & timeouts

rate_limited

429. Usually the per-API-key REST rate limit (600 requests/minute) was exceeded; on Google-backed endpoints (/gsc/*) it can also relay Google’s own rate limit. Retry-After (seconds) is present when Searchable’s limiter applied the limit and may be absent for an upstream limit; both carry retryable: true. See Rate limiting. Over MCP there is no 429: a tool returns rate_limited once a key or OAuth connection exceeds 600 tool calls per minute, with retryable: true and retryAfterSeconds in the error instead of a Retry-After header. The source-scout cooldown from manage_associated_source (where Associated Sources is enabled) also returns rate_limited over MCP, without retryable. Fix: Over REST, back off for Retry-After seconds when present (otherwise about a minute), then retry. Over MCP, retry the tool-call limit after retryAfterSeconds, but never retry the source-scout cooldown automatically. Spread bursts of requests across a longer window rather than firing them all at once.

idempotency_in_flight

409. Another request carrying the same Idempotency-Key is still executing — the duplicate was refused instead of running the side effect a second time. Carries a Retry-After header (5 seconds) and retryable: true. See Idempotency. Fix: Wait Retry-After seconds, then retry with the same key — once the original completes successfully you’ll receive its stored response (X-Idempotent-Replay: true); if the original failed, the retry runs fresh.

idempotency_conflict

409. The Idempotency-Key was already used for a request with a different body, so replaying the stored response would answer a different request. See Idempotency. Fix: Use a new key for a different request; reuse a key only to retry the identical request.

audit_lock_busy

409. POST /audits waited briefly for another audit of the same project (started in the app or through the API) to finish starting, and it was still starting. Nothing was started. Carries a Retry-After header (2 seconds) and retryable: true. The response is not stored against an Idempotency-Key. Fix: Wait Retry-After seconds, then retry the same request (the same key is fine). Pages the other audit started come back counted in skippedInFlightCount.

query_timeout

504. The query took longer than the endpoint’s timeout budget. Carries timeout: true and retryable: true. Fix: Narrow the days window or add filters (platform, topic, location), then retry.

Google integrations

gsc_not_connected

409. No Google Search Console site is linked to this project. Fix: Connect Google Search Console under Settings → Integrations, then retry.

gsc_site_not_linked

404. The project has a GSC site reference that doesn’t resolve — a narrower variant of “not connected” surfaced by some GSC endpoints. Fix: Reconnect Google Search Console under Settings → Integrations.

gsc_access_denied

403. Google denied the request — the connected account’s token was revoked, permission to the property was removed, or the property was unshared. Distinct from gsc_not_connected: the integration was connected, but Google is now rejecting it. Fix: Reconnect the Google account under Settings → Integrations.

ga4_not_connected

409. No Google Analytics 4 property is linked to this project. Fix: Connect Google Analytics 4 under Settings → Integrations, then retry.

AI Traffic

traffic_not_connected

409. No crawler-log, CDN, or Searchable-tracker data source is connected for this project. Fix: Connect a source under AI Traffic → Setup.

Analytics filters

These codes come from the Searchable app’s own session-authenticated routes. Analytics routes (the dashboard’s data endpoints, and the report snapshots built from them) accept a filterSpec query parameter; every other session-authenticated app route refuses one with filters_unsupported. The REST API surfaces the rest of this page covers never return these codes.

filters_invalid

400. The filterSpec query parameter is malformed — undecodable, an unrecognized version, an unknown dimension or operator, the same dimension named twice in one AND-group, a mix of a legacy per-dimension param with filterSpec for the same dimension (or with an OR-joined filterSpec), the filterSpec parameter repeated, or a join the endpoint doesn’t allow. Fix: Check the filter payload’s encoding and make sure you’re not sending both a legacy filter param and a filterSpec condition for the same dimension.

filters_unsupported

400. The decoded filter set names a dimension, operator, or ref this endpoint doesn’t support, or the endpoint doesn’t support filterSpec at all. Fix: Check the endpoint’s documented supported dimensions and operators, and drop anything else from the request.

filters_too_large

400. The filter set exceeds a bound — too many values in one condition, too many conditions, or the URL-encoded filterSpec payload exceeds the 10 KB size limit. Fix: Reduce the number of values or conditions, or split the request.

filters_not_enabled

403. Filters beyond the basic “is any of” aren’t enabled for this user on this project. Requests using only “is any of” conditions are never refused this way. Fix: This is a rollout gate, not a plan limit — contact support@searchable.com if you believe it should be enabled.

filters_flag_unavailable

503. The feature flag controlling analytics filters couldn’t be evaluated. Fix: Retry the request. If it persists, contact support@searchable.com.

filter_rules_bounds_exceeded

400. A request’s metric rules (Your Prompts volume, difficulty, visibility score and similar) exceed a per-request bound: they span more than one project, there are too many metric rules in one request, or a rule resolves to more ids than the cap. Reserved — no endpoint returns it yet. Fix: Apply metric rules to a single project, use fewer of them, or narrow each rule, then retry.

filter_compile_invariant

500. An internal invariant was violated while compiling filters into a query (an unexpectedly empty value set, an unmapped dimension, or an unsupported operator reaching the compiler). Never caused by anything in your request. Fix: Retry. If it persists, contact support@searchable.com with the requestId.

Server errors

internal_error

500. An unexpected server-side failure. Never leaks internal error text — the message is always a stable, generic description. Fix: Retry with exponential backoff. If it persists, contact support@searchable.com with the requestId from the response body.

Need more?

Higher rate limits, a code not listed here, or a question about a specific response? Contact support@searchable.com with the endpoint, requestId, and response body.