> ## Documentation Index
> Fetch the complete documentation index at: https://docs.searchable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API Error Reference

> Every error code the Searchable REST API returns, what it means, and how to fix it

## Overview

Every error response from the REST API (`/api/mcp/*`, `/api/v1/*`, and the Looker Studio connector) is shaped as [`application/problem+json`](https://www.rfc-editor.org/rfc/rfc9457) — a machine-readable envelope that additively extends the API's original `{ error: "..." }` shape. Existing integrations that only read `error` keep working unmodified.

```json theme={null}
{
  "type": "https://docs.searchable.com/api/errors#not_found",
  "title": "Not Found",
  "status": 404,
  "code": "not_found",
  "error": "Project not found or access denied",
  "message": "Project not found or access denied",
  "howToFix": "...",
  "requestId": "b3f1c2a0-4e9d-4a11-9c3a-8e2f6d1a7c55"
}
```

| Field       | Meaning                                                                                             |
| ----------- | --------------------------------------------------------------------------------------------------- |
| `type`      | A URL identifying the error kind — always this page, anchored to `code`                             |
| `title`     | Human-readable summary of `code`                                                                    |
| `status`    | The HTTP status code (matches the response status)                                                  |
| `code`      | Machine-readable error code — stable, safe to branch on                                             |
| `error`     | **Legacy compat key.** Always equal to `message`. Existing integrations that read this keep working |
| `message`   | Human-readable error message (identical to `error`)                                                 |
| `howToFix`  | Present on actionable errors — a concrete next step                                                 |
| `requestId` | The request's id. Include this when contacting support                                              |

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

| Code                                              | Status  | Meaning                                                                       |
| ------------------------------------------------- | ------- | ----------------------------------------------------------------------------- |
| [`unauthorized`](#unauthorized)                   | 401     | Missing, malformed, revoked, or expired API key                               |
| [`forbidden`](#forbidden)                         | 403     | Access denied to the requested resource                                       |
| [`missing_scope`](#missing-scope)                 | 403     | The API key lacks the required `read`/`write` scope                           |
| [`plan_upgrade_required`](#plan-upgrade-required) | 403     | The workspace's plan doesn't include this feature                             |
| [`not_found`](#not-found)                         | 404     | The resource doesn't exist, or the key can't reach it                         |
| [`invalid_argument`](#invalid-argument)           | 400     | A query parameter, path parameter, or body field is invalid                   |
| [`invalid_country`](#invalid-country)             | 400     | An unrecognized `country` filter value                                        |
| [`no_domain`](#no-domain)                         | 400     | The project has no verified domain configured                                 |
| [`no_workspace`](#no-workspace)                   | 400/403 | The project isn't linked to a workspace                                       |
| [`no_pages`](#no-pages)                           | 400     | No tracked (or matching) pages to audit                                       |
| [`bulk_limit_exceeded`](#bulk-limit-exceeded)     | 400     | Too many pages requested for a bulk audit on this plan                        |
| [`quota_exceeded`](#quota-exceeded)               | 403     | A monthly/concurrent usage quota is exhausted                                 |
| [`project_not_runnable`](#project-not-runnable)   | 403     | The project is paused or archived — background jobs are blocked               |
| [`pitch_not_supported`](#pitch-not-supported)     | 403     | The feature isn't available on pitch (scoped) projects                        |
| [`rate_limited`](#rate-limited)                   | 429     | Too many requests — per-key REST rate limit, or a per-project concurrency cap |
| [`idempotency_in_flight`](#idempotency-in-flight) | 409     | A request with the same `Idempotency-Key` is still running                    |
| [`query_timeout`](#query-timeout)                 | 504     | The query took too long — narrow the request and retry                        |
| [`gsc_not_connected`](#gsc-not-connected)         | 409     | No Google Search Console site linked to the project                           |
| [`gsc_site_not_linked`](#gsc-site-not-linked)     | 404     | The linked GSC site record doesn't match the project                          |
| [`gsc_access_denied`](#gsc-access-denied)         | 403     | Google revoked/denied access — reconnect the account                          |
| [`ga4_not_connected`](#ga4-not-connected)         | 409     | No Google Analytics 4 property linked to the project                          |
| [`traffic_not_connected`](#traffic-not-connected) | 409     | No crawler-log, CDN, or tracker source connected                              |
| [`internal_error`](#internal-error)               | 500     | An unexpected server error — retry, then contact support                      |

## 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 **Settings → Workspace → Integrations**.

### `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, or the project is inactive).

**Fix:** Confirm the key's owning user is a member of the workspace that owns the resource.

### `missing_scope`

**403.** The key doesn't carry the scope the endpoint requires. Read endpoints require `read`; write/action endpoints (report generation, triggering an audit, refreshing a sitemap) require `write`. `admin` covers both.

**Fix:** Create a new key with the required scope, or use a key that already has it. See [Scopes](/advanced/api-usage#scopes).

### `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**.

## 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](/advanced/api-usage#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.

### `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](mailto: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`.

**Fix:** Wait for the quota to reset (monthly quotas), reduce concurrent usage, or upgrade the plan.

### `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, and sitemap sync are regular-project-only features.

**Fix:** This endpoint only supports regular (non-pitch) projects.

## Rate limiting & timeouts

### `rate_limited`

**429.** Either the per-API-key REST rate limit (600 requests/minute) was exceeded, or — on some endpoints — a per-project concurrency cap was already held by another in-flight request from the same key. The response carries a `Retry-After` header (seconds) and `retryable: true`. See [Rate limiting](/advanced/api-usage#rate-limiting).

**Fix:** Back off for `Retry-After` seconds, then retry. 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](/advanced/api-usage#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.

### `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**.

## 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](mailto: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](mailto:support@searchable.com)** with the endpoint, `requestId`, and response body.
