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

# Error Codes

> Every API error code, its HTTP status, and how to fix it.

All errors share one envelope. `details` appears only on errors that carry structured context (validation, suppression, scope):

```json theme={null}
{
  "error": {
    "code": "recipient_suppressed",
    "message": "Cannot send email. Recipient gone@example.com is suppressed (hard_bounce).",
    "details": { "email": "gone@example.com", "reason": "hard_bounce" }
  }
}
```

Raw provider, database, and driver errors are never serialized — you only ever see a stable `code` plus a plain-language `message`.

## Authentication & authorization

| Code                     | Status | Meaning & fix                                                                                                          |
| :----------------------- | :----- | :--------------------------------------------------------------------------------------------------------------------- |
| `unauthorized`           | 401    | Missing or malformed `Authorization: Bearer` header. Check the header format.                                          |
| `invalid_api_key_format` | 401    | Key does not start with `gork_`. Keys look like `gork_live_…` or `gork_test_…`.                                        |
| `invalid_api_key`        | 401    | Unknown or revoked key. Re-check the value or create a replacement.                                                    |
| `insufficient_scope`     | 403    | Key lacks the required scope. Response names `requiredScope` vs `grantedScopes` — create a key with the missing scope. |
| `inbox_access_denied`    | 403    | Inbox-bound key touched another inbox (or tried `POST /v1/inboxes`). Use the right bound key or a full-org key.        |
| `rate_limit_exceeded`    | 429    | 240 req/min/workspace exceeded. Back off and retry — see [Rate Limits](/rate-limits).                                  |

## Inboxes

| Code                     | Status | Meaning & fix                                                                                                    |
| :----------------------- | :----- | :--------------------------------------------------------------------------------------------------------------- |
| `apex_domain_reserved`   | 403    | `@gork.email` is reserved for platform operations. Use `@try.gork.email` or a verified custom domain.            |
| `domain_not_verified`    | 400    | The custom domain is not `verified` for your workspace. Finish DNS and run `POST /v1/domains/{id}/verify`.       |
| `username_reserved`      | 422    | The username is brand- or role-protected. Pick another name.                                                     |
| `address_already_exists` | 409    | The address is live on another inbox. Pick another username.                                                     |
| `address_in_cooldown`    | 409    | The address was recently deactivated and sits in a 90-day quarantine. Response includes `cooldownDaysRemaining`. |
| `inbox_not_found`        | 404    | No active inbox with that id in your workspace.                                                                  |
| `agent_not_found`        | 404    | The `agentId` on inbox creation does not exist in your workspace.                                                |

## Sending

| Code                         | Status | Meaning & fix                                                                                                                                                                  |
| :--------------------------- | :----- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `payload_too_large`          | 413    | Body plus decoded attachments exceed 15 MB. Shrink the content or link files instead.                                                                                          |
| `attachment_too_large`       | 413    | A single file or the attachment total exceeds the per-send file limits.                                                                                                        |
| `sending_not_approved`       | 403    | Workspace sending is paused (manual review or reputation tripwire). Response may include `reason`, `action`, `trippedAt` — request approval in the console or contact support. |
| `limit_reached`              | 402    | Monthly/daily allowance or an explicit workspace cap is exhausted. Enable overage or upgrade.                                                                                  |
| `spend_cap_reached`          | 402    | The spend-protection pause threshold was hit. Raise or remove the cap in **Billing → Spend protection**.                                                                       |
| `recipient_suppressed`       | 422    | A recipient is suppressed. Response `details` carry `email`, `reason`, `source`. Remove the suppression or drop the recipient.                                                 |
| `sandbox_bulk_not_allowed`   | 403    | Free tier is 1-on-1 only (one `to`, no `cc`/`bcc`). Send individually or upgrade.                                                                                              |
| `sending_paused_bulk_review` | 403    | Broadcast pattern tripped (50+ unique recipients in 10 minutes). Contact support — conversational replies are unaffected.                                                      |
| `email_delivery_failed`      | 502    | The provider failed after quota was claimed; quota was restored. Retry in a moment.                                                                                            |
| `provider_unavailable`       | 503    | Sending backend unreachable. Retry with backoff.                                                                                                                               |
| `attachments_unsupported`    | 400    | The active provider cannot send attachments. Send a link instead.                                                                                                              |

## Drafts & scheduling

| Code                          | Status | Meaning & fix                                                                                         |
| :---------------------------- | :----- | :---------------------------------------------------------------------------------------------------- |
| `draft_limit_reached`         | 402    | Too many open drafts in the workspace. Send or discard some first.                                    |
| `draft_not_found`             | 404    | No draft with that id in your workspace.                                                              |
| `draft_not_editable`          | 409    | Only `draft`-status rows can be edited.                                                               |
| `draft_already_sent`          | 409    | Draft already sent; the send endpoint returns the existing message idempotently.                      |
| `draft_discarded`             | 409    | Discarded drafts cannot be sent. Duplicate it into a new draft.                                       |
| `draft_incomplete`            | 400    | Add at least one recipient before sending.                                                            |
| `attachments_not_schedulable` | 400    | Scheduled sends cannot carry attachments yet. Send now or schedule without files.                     |
| `not_scheduled`               | 409    | `DELETE /v1/messages/{id}/schedule` on a message whose status is not `scheduled` — nothing to cancel. |
| `message_not_found`           | 404    | No message with that id in your workspace.                                                            |

## Webhooks, keys, domains, threads, attachments, suppressions

| Code                        | Status | Meaning & fix                                                                 |
| :-------------------------- | :----- | :---------------------------------------------------------------------------- |
| `webhook_not_found`         | 404    | No webhook with that id in your workspace.                                    |
| `webhook_limit_reached`     | 402    | Plan endpoint quota exhausted. Delete an endpoint or upgrade.                 |
| `key_not_found`             | 404    | No API key with that id in your workspace.                                    |
| `invalid_scopes`            | 400    | Inbox-bound keys may only hold mail scopes. Narrow the request.               |
| `domain_not_found`          | 404    | No domain with that id in your workspace.                                     |
| `domain_already_registered` | 409    | Domain is already registered. Verify the existing row instead.                |
| `thread_not_found`          | 404    | No thread with that id in your workspace.                                     |
| `attachment_not_found`      | 404    | No attachment with that id in your workspace.                                 |
| `suppression_not_found`     | 404    | No suppression with that id in your workspace.                                |
| `missing_token`             | 400    | `/v1/unsubscribe` called without a token.                                     |
| `invalid_or_expired_token`  | 400    | Unsubscribe token is invalid or expired. Request a fresh link.                |
| `validation_failed`         | 400    | Request body failed schema validation. Response `details` pinpoint the field. |

## Debugging guidance

* **`invalid_api_key` right after creating a key:** you are sending the prefix, not the raw key. Only the one-time `apiKey` value authenticates.
* **`insufficient_scope`:** read `requiredScope` in the response and mint a key that includes it — do not downgrade the endpoint call.
* **`recipient_suppressed`:** query `GET /v1/suppressions?reason=…` for the address; `details.source` tells you whether a provider event or a human added it.
* **`limit_reached` vs `spend_cap_reached`:** both are 402, but the fix differs — overage/upgrade for the former, **Billing → Spend protection** for the latter.
* **`sending_not_approved`:** check `reason`/`action` in the response for the tripwire that fired before contacting support.
* **`email_delivery_failed`:** safe to retry — quota was refunded automatically.
