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

# Errors

> Stable error codes, status codes, and what to do about each.

Every error response is JSON in this shape:

```json theme={null}
{
  "error": {
    "code": "org_not_found",
    "message": "Org \"acme\" does not exist.",
    "request_id": "req_e1e3f521-c291-40d3-9edb-b5875ef5b417",
    "doc_url": "https://docs.goparlay.io/errors#org_not_found",
    "details": { "org_id": "acme" }
  }
}
```

The `code` field is **stable** — write your error-handling logic against `code`, not `message` (messages may improve; codes are the contract).

The `request_id` should be included in any support ticket — we can pull the full server log from it.

## Quick remediation table

### Auth (401 / 403)

| Code                       | What it means                                | What to do                                  |
| -------------------------- | -------------------------------------------- | ------------------------------------------- |
| `authentication_required`  | No `Authorization` header                    | Set `Authorization: Bearer <key>`           |
| `invalid_api_key`          | Key doesn't match any active partner         | Check for typos; rotate via dashboard       |
| `key_revoked`              | Key was revoked                              | Generate a new key, update your env var     |
| `key_environment_mismatch` | Live key on sandbox URL or vice versa        | Match the key prefix to the base URL        |
| `scope_required`           | Endpoint needs a scope your key doesn't have | Request the scope from your account manager |

### Validation (400 / 422)

| Code                    | Status | What to do                                                           |
| ----------------------- | ------ | -------------------------------------------------------------------- |
| `invalid_request`       | 400    | Body doesn't match the schema. See `details` for the offending field |
| `missing_field`         | 400    | Required field omitted. See `details.field`                          |
| `invalid_enum`          | 400    | Field value not in allowed enum. See `details.allowed`               |
| `invalid_url`           | 400    | URL is malformed                                                     |
| `invalid_recording_url` | 422    | Recording URL not reachable / not a supported codec                  |
| `recording_too_long`    | 422    | Audio over 60 minutes — split it                                     |
| `recording_too_short`   | 422    | Audio under 10 seconds — likely a bad upload                         |
| `unsupported_codec`     | 422    | Use MP3, WAV, M4A, or FLAC                                           |

### Not found (404)

| Code                  | What to do                                                                 |
| --------------------- | -------------------------------------------------------------------------- |
| `org_not_found`       | Verify `org_id`; case-sensitive. Use `list_orgs` / `GET /v1/orgs`          |
| `rep_not_found`       | Verify `rep_id` within the right `org_id`                                  |
| `analysis_not_found`  | Verify the UUID. May be soft-deleted — purge can be reversed only manually |
| `playbook_not_found`  | Soft-deleted or wrong org                                                  |
| `webhook_not_found`   | Webhook archived                                                           |
| `prompt_not_found`    | Soft-deleted or wrong org                                                  |
| `draft_not_found`     | Verify the draft UUID exists in this org                                   |
| `synthesis_not_found` | No synthesis exists yet — call `generate_rep_synthesis` first              |

### Conflict (409)

| Code                          | What to do                                                      |
| ----------------------------- | --------------------------------------------------------------- |
| `idempotency_key_reused`      | Same idempotency key + different payload. Generate a fresh UUID |
| `analysis_already_processing` | Don't re-submit the same recording\_url; check status first     |
| `analysis_cannot_rescore`     | Rescore not yet supported on this analysis type                 |
| `draft_already_published`     | Draft was already promoted to a playbook                        |
| `webhook_disabled`            | Webhook is paused — un-pause via `update_webhook` first         |

### Limits (429)

| Code                        | What to do                                                                           |
| --------------------------- | ------------------------------------------------------------------------------------ |
| `rate_limited`              | Sleep `Retry-After` seconds, then retry. The MCP client does this automatically once |
| `quota_exceeded`            | Plan / billing action required                                                       |
| `sandbox_limit_reached`     | You hit a sandbox-only daily cap. Wait or move to live keys                          |
| `concurrency_limit_reached` | Too many in-flight async jobs. Wait for some to complete                             |

### Pipeline (502 / 504)

| Code                   | Status | What to do                                                         |
| ---------------------- | ------ | ------------------------------------------------------------------ |
| `transcription_failed` | 502    | Deepgram failed. Retry once; check audio quality                   |
| `analysis_failed`      | 502    | Gemini returned bad output. Retry once; ping support if persistent |
| `upstream_unavailable` | 502    | Deepgram or Gemini outage. Retry with backoff                      |
| `upstream_timeout`     | 504    | Job exceeded internal time budget. Retry; check audio length       |

### Compliance (422 / 403)

| Code                         | Status | What to do                                                                              |
| ---------------------------- | ------ | --------------------------------------------------------------------------------------- |
| `pii_policy_violation`       | 422    | Recording contains PII you've configured to reject. Adjust org policy or scrub the file |
| `retention_policy_violation` | 422    | Operation blocked by your retention rules                                               |
| `healthcare_without_baa`     | 403    | Healthcare data requires a signed BAA. Contact account manager                          |

### Partner state (404 / 403)

| Code                | What it means                                                   |
| ------------------- | --------------------------------------------------------------- |
| `partner_not_found` | Key resolves to a partner record we can't find. Contact support |
| `partner_suspended` | Account suspended. Billing or compliance issue — check email    |

### Server (500 / 501)

| Code              | What to do                                                                      |
| ----------------- | ------------------------------------------------------------------------------- |
| `internal_error`  | Bug on our side. Retry once; if persistent, file a ticket with the `request_id` |
| `not_implemented` | Endpoint is reserved for a future release                                       |

## Retry strategy

The MCP server retries automatically on:

* Any 5xx
* `rate_limited` (with `Retry-After` header honored, max once)
* `transcription_failed`, `analysis_failed`, `upstream_timeout`, `upstream_unavailable` (max once)

It does **not** retry on auth, validation, conflict, not-found, or quota errors — those need code/config changes, not retries.

Custom REST integrations should follow the same pattern.
