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

# Error Handling

> Error response format and the catalog of error codes returned by the API.

All errors return JSON with a consistent shape:

```json theme={null}
{
  "error": {
    "code": "namespace.specific_code",
    "message": "human-readable message",
    "meta": { "field": "..." }
  },
  "request_id": "a1b2c3d4"
}
```

Use `error.code` for programmatic handling; use `error.message` for logging and display. Some errors include an `error.meta` object with structured context (for example `meta.field` for a validation failure, `meta.plan` and `meta.max` for billing limits). Codes are stable and namespaced with dot-separated segments, so you can match on a prefix (for example `platform.*` for any upstream platform failure, or `byok.*` for any bring-your-own-key issue).

The `request_id` field is present on every error response. The same value appears in the `X-Request-ID` response header. Include it when reporting an issue to support. See [Debugging with request IDs](/guides/debugging) for details on how to supply your own correlation ID.

## Code namespaces

The first segment of the code tells you where the failure originated.

| Namespace                                                    | Meaning                                                                                                       |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| `auth.*`                                                     | Authentication and authorization at the SocialAPI layer                                                       |
| `account.*`                                                  | Connected account issues (linking, state, supported features)                                                 |
| `platform.<name>.*`                                          | The upstream platform (Instagram, TikTok, etc.) returned an error                                             |
| `validation.*`                                               | Request body, query parameter, or path parameter is invalid                                                   |
| `resource.*`                                                 | Generic resource lookup, conflict, or capability error                                                        |
| `billing.*`                                                  | Plan limit reached or feature gated by tier                                                                   |
| `brand.*`, `media.*`, `post.*`, `conversation.*`, `invite.*` | Resource-specific errors on those object types                                                                |
| `byok.*`                                                     | Bring-your-own-key credentials (Twitter app, etc.)                                                            |
| `publishing.delivery.*`                                      | A platform delivery worker failed mid-publish                                                                 |
| `whatsapp.*`                                                 | WhatsApp Cloud API specific failures (window, frequency, template state)                                      |
| `telegram.*`                                                 | Telegram Bot API specific failures (e.g. the bot cannot message a user who has not started it)                |
| `system.internal`                                            | Unexpected server error; safe to retry idempotent requests                                                    |
| `http.<status>`                                              | Fiber framework HTTP error (for example, `http.404` for an unknown route). Not returned by application logic. |

## Authentication

| HTTP | Code                           | When it occurs                                                                                                                                                         |
| ---- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401  | `auth.missing_header`          | `Authorization` header is absent                                                                                                                                       |
| 401  | `auth.empty_token`             | `Authorization: Bearer` with no token value                                                                                                                            |
| 401  | `auth.invalid_key`             | API key does not match any active key                                                                                                                                  |
| 401  | `auth.invalid_token`           | Bearer JWT or OAuth access token is invalid or expired                                                                                                                 |
| 403  | `auth.invalid_secret`          | Provided client secret does not match                                                                                                                                  |
| 401  | `auth.not_authenticated`       | Caller is unauthenticated for an endpoint that requires it                                                                                                             |
| 401  | `auth.not_provisioned`         | Dashboard user has no `users` row yet; call `POST /v1/users/provision` first                                                                                           |
| 401  | `auth.user_not_found`          | Token resolves to a user that no longer exists                                                                                                                         |
| 403  | `auth.dashboard_only`          | Endpoint accepts only dashboard JWTs (raw API keys are rejected)                                                                                                       |
| 403  | `auth.requires_dashboard_auth` | Same as above for endpoints behind brand-level UI                                                                                                                      |
| 403  | `auth.insufficient_scope`      | A [scoped API key](/guides/scoped-keys) lacks the required scope, or a restricted key called an admin operation. `meta.required_scope` names the scope when applicable |
| 403  | `auth.state_mismatch`          | OAuth callback `state` does not match the issued value                                                                                                                 |
| 400  | `auth.oauth.invalid_client`    | OAuth 2.1 client lookup failed during MCP token exchange                                                                                                               |
| 400  | `auth.oauth.invalid_redirect`  | `redirect_uri` is not in the client's whitelist                                                                                                                        |
| 400  | `auth.oauth.request_expired`   | Authorization request expired before exchange                                                                                                                          |
| 401  | `auth.oauth.user_missing`      | OAuth grant references a user that does not exist                                                                                                                      |

## Accounts

| HTTP | Code                            | When it occurs                                           |
| ---- | ------------------------------- | -------------------------------------------------------- |
| 404  | `account.not_found`             | The account ID does not belong to your workspace         |
| 409  | `account.already_linked`        | This social account is already connected to a workspace  |
| 403  | `account.reconnection_required` | OAuth token is no longer usable; rerun the OAuth flow    |
| 400  | `account.unsupported_platform`  | Platform value is not recognized                         |
| 400  | `account.no_active_page`        | Account has no active Page (Facebook, LinkedIn) selected |
| 400  | `account.page_inactive`         | The requested Page is currently disabled                 |
| 400  | `account.pages_not_supported`   | Pages do not apply to this platform                      |

## Validation

| HTTP | Code                                | When it occurs                                                                                 |
| ---- | ----------------------------------- | ---------------------------------------------------------------------------------------------- |
| 400  | `validation.field_required`         | A required field is missing from the request                                                   |
| 400  | `validation.field_invalid`          | A field has the wrong type, format, or value                                                   |
| 400  | `validation.body_invalid`           | Request body could not be parsed as JSON                                                       |
| 400  | `validation.redirect_uri_duplicate` | Redirect URI is already registered for this client                                             |
| 400  | `validation.scope_invalid`          | A [key](/guides/scoped-keys) was created or edited with an unknown scope; the message names it |
| 400  | `validation.brand_invalid`          | A key's `brand_ids` references a brand that does not exist or that you do not own              |

The `code` always starts with `validation.`. The specific sub-code after the dot identifies the field or rule that failed; `error.meta.field` in the response points to the offending field name when applicable.

## Resources

| HTTP | Code                                                                             | When it occurs                                                                           |
| ---- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| 404  | `resource.not_found`                                                             | Generic resource lookup miss; check the `error.meta.resource` and `error.meta.id` fields |
| 409  | `resource.conflict`                                                              | Resource is in a state that conflicts with the requested action                          |
| 410  | `resource.gone`                                                                  | Resource existed but was permanently removed                                             |
| 501  | `resource.not_supported`                                                         | Operation does not apply to this platform                                                |
| 404  | `brand.not_found`, `media.not_found`, `conversation.not_found`, `post.not_found` | Specific lookups failed                                                                  |
| 409  | `media.in_use`                                                                   | Cannot delete media that is still referenced by a post                                   |
| 410  | `invite.expired`                                                                 | Brand invite token is past its expiry                                                    |
| 400  | `invite.invalid`                                                                 | Brand invite token is malformed or already consumed                                      |
| 409  | `post.state_invalid`                                                             | Action does not apply to a post in this state (e.g. retry on a `published` post)         |
| 400  | `post.no_retryable_deliveries`                                                   | No failed deliveries on this post to retry                                               |

### Invite conflicts and validation

`POST /v1/invites` returns `409 resource.conflict` for two different situations, distinguished by the `message`:

* `an active invite already exists for this brand and platform`
* `brand already has a connected account for this platform`

Both share the `resource.conflict` code. Note that `GET /v1/invites` and `GET /v1/accounts` query different objects, so an empty invite list does not guarantee that a create will succeed: the brand may already have a connected account for that platform. Check both before treating a create as safe.

`expires_in_days` is required on invite creation and must be one of `1`, `3`, `7`, `14`, or `30`. Any other value (including omitting it) returns `400 validation.field_invalid` with the message `expires_in_days must be one of: 1, 3, 7, 14, 30`.

## Billing and plan limits

| HTTP | Code                                          | When it occurs                                 |
| ---- | --------------------------------------------- | ---------------------------------------------- |
| 403  | `billing.brands_limit`, `brand.limit_reached` | Brand count would exceed your plan's allowance |
| 429  | `billing.post_limit`                          | Monthly post allowance is exhausted            |
| 413  | `billing.storage_quota`                       | Media upload would exceed your storage limit   |
| 403  | `billing.platform_not_available`              | Platform requires a higher plan tier           |
| 429  | `billing.export_limit`                        | Daily export allowance is exhausted            |
| 429  | `billing.export_concurrent`                   | Too many exports running at once for this plan |
| 429  | `billing.export_cooldown`                     | Cooldown active between exports                |

Limit responses include `error.meta.plan` and `error.meta.max` so clients can prompt the user to upgrade.

## Platform errors

When the upstream platform (Meta, TikTok, Google, etc.) rejects a call, the error code has the form `platform.<name>.<reason>`. The HTTP status is forwarded when possible; otherwise it is `502 Bad Gateway`.

| HTTP | Code                                                                                                                                                               | When it occurs                                                                   |
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| 401  | `platform.<name>.auth`                                                                                                                                             | Platform rejected the stored OAuth token; the account needs to be reconnected    |
| 429  | `platform.<name>.rate_limit`                                                                                                                                       | Upstream platform rate limit hit. This does **not** consume your SocialAPI quota |
| 502  | `platform.<name>.api_error`                                                                                                                                        | Generic upstream API failure; check the `error` message for detail               |
| 400  | `platform.tiktok.media_required`                                                                                                                                   | TikTok requires media for this post type                                         |
| 403  | `platform.instagram.permission_denied`, `platform.whatsapp.permission_denied`                                                                                      | OAuth scopes do not include the action                                           |
| 410  | `platform.instagram.window_closed`                                                                                                                                 | Reply window for this comment or DM has expired                                  |
| 404  | `platform.facebook.no_pages`, `platform.linkedin.no_pages`, `platform.google.no_locations`, `platform.youtube.no_channels`, `platform.youtube.no_uploads_playlist` | The connected account exposes no usable target                                   |

## BYOK (bring-your-own-key)

For platforms that require a customer-supplied OAuth app (currently Twitter / X).

| HTTP | Code                         | When it occurs                                                             |
| ---- | ---------------------------- | -------------------------------------------------------------------------- |
| 412  | `byok.credentials_missing`   | No credentials configured for the user or brand; show the BYOK setup modal |
| 401  | `byok.credentials_invalid`   | Stored credentials were rejected by the platform                           |
| 403  | `byok.credentials_forbidden` | Caller does not own the brand they are configuring                         |
| 429  | `byok.test_rate_limited`     | Too many credential test calls within a minute                             |

## Publishing delivery

These are emitted by the post delivery workers and surface in the inbox / events stream rather than as direct HTTP responses, but they appear in `event.error_code` for `post.failed` and `post.partial` events.

| Code                                      | Meaning                                                   |
| ----------------------------------------- | --------------------------------------------------------- |
| `publishing.delivery.account_load_failed` | Could not load the connected account at delivery time     |
| `publishing.delivery.page_resolve_failed` | Could not resolve the target Page (Facebook, LinkedIn)    |
| `publishing.delivery.timeout`             | Delivery exceeded the per-platform timeout                |
| `publishing.duplicate_target`             | Two deliveries targeting the same destination were queued |

## WhatsApp

WhatsApp Cloud API has a richer error surface than other platforms because of its messaging window, frequency caps, and template state machine.

| HTTP | Code                           | When it occurs                                                               |
| ---- | ------------------------------ | ---------------------------------------------------------------------------- |
| 410  | `whatsapp.window_closed`       | 24-hour customer service window is closed; send an approved template instead |
| 429  | `whatsapp.frequency_cap`       | Per-user marketing cap reached                                               |
| 429  | `whatsapp.pair_rate_limit`     | Per phone-number-pair rate limit hit                                         |
| 429  | `whatsapp.throughput_exceeded` | Account-level throughput exhausted                                           |
| 410  | `whatsapp.user_opted_out`      | Recipient has opted out of marketing messages                                |
| 410  | `whatsapp.undeliverable`       | Message could not be delivered (cold number, region block, etc.)             |
| 404  | `whatsapp.template_not_found`  | Referenced template does not exist for this WABA                             |
| 409  | `whatsapp.template_paused`     | Template is paused due to quality drop                                       |
| 404  | `whatsapp.no_account`          | No WhatsApp account is connected for this request                            |
| 500  | `whatsapp.unknown`             | Unmapped WhatsApp error; check the message for raw detail                    |

## Telegram

| HTTP | Code                      | When it occurs                                                                                                                                   |
| ---- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| 403  | `telegram.cannot_message` | The bot cannot message this chat. The user must start the bot first, or the bot was blocked or removed. There is no 24-hour window once started. |

## System

| HTTP | Code              | When it occurs                                                                     |
| ---- | ----------------- | ---------------------------------------------------------------------------------- |
| 500  | `system.internal` | Unexpected server error. Retry idempotent requests; if persistent, contact support |

## Handling common cases

### Reconnect when a token dies

`account.reconnection_required` and `platform.<name>.auth` both mean the stored OAuth token is no longer usable. Send the user back through `GET /v1/oauth/url`.

```python theme={null}
err = response.json()["error"]
code = err["code"]
if code == "account.reconnection_required" or \
   (code.startswith("platform.") and code.endswith(".auth")):
    redirect_to_reconnect(account_id)
```

### Storage quota

`billing.storage_quota` (HTTP 413) means the upload would push you past your plan's storage limit. Check current usage with `GET /v1/media/storage` and delete unused media or upgrade.

### Monthly post or interaction limit

`billing.post_limit` (HTTP 429) means your monthly allowance is exhausted. Limits reset at the start of your next billing period. The free plan uses a rolling 30-day window from your account anniversary.

### Platform throttling

`platform.<name>.rate_limit` (HTTP 429) is the upstream platform throttling you, not SocialAPI. It does not consume your quota. Wait a few minutes and retry with backoff.

### Validation failures

Any `validation.*` code includes an `error.meta.field` pointing to the offending input. Surface the field name to the user.

## Example error handling

```typescript theme={null}
const resp = await fetch(`https://api.social-api.ai/v1/accounts/${accountId}/comments`, {
  headers: { Authorization: `Bearer ${apiKey}` }
});

if (!resp.ok) {
  const body = await resp.json();
  const { code, message, meta } = body.error;
  const requestId = body.request_id; // include in support reports

  // Reconnect dead OAuth tokens
  if (code === "account.reconnection_required" ||
      (code.startsWith("platform.") && code.endsWith(".auth"))) {
    await reconnectAccount(accountId);
    return;
  }

  // Upstream platform throttling: retry with backoff
  if (code.startsWith("platform.") && code.endsWith(".rate_limit")) {
    await sleep(60_000);
    return retry();
  }

  // Plan limits: prompt to upgrade
  if (code.startsWith("billing.")) {
    throw new Error(`Plan limit hit: ${message}`);
  }

  // BYOK setup needed
  if (code === "byok.credentials_missing") {
    showByokSetupModal(meta?.platform);
    return;
  }

  // Operation not supported on this platform
  if (code === "resource.not_supported") {
    console.warn(`Not supported: ${message}`);
    return null;
  }

  throw new Error(`API error: ${message} (${code}) [request_id: ${requestId}]`);
}
```
