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

# Errors & rate limits

> Error shape, HTTP status mapping, retry policy.

## Error response shape

All `/api/v1/*` errors return JSON with this shape:

```json theme={null}
{
  "error": "Human-readable message",
  "error_type": "token_expired"
}
```

Some errors carry extra fields:

* **`actionable: true`** — the user can fix the cause themselves. Always present
  with a `hint`. Example: a LinkedIn token expired in MagicPost.
* **`hint: "Tell the user to reconnect LinkedIn from MagicPost > Settings"`** —
  the exact message to surface in your UI / LLM conversation.
* **`existing: [...]`** — present on schedule warnings; the list of posts
  already on the same day or in the same slot.

## Error type catalog

| `error_type` | HTTP | What it means |
| - | - | - |
| `auth_required` | 401 | No Bearer token, or token doesn't start with `mp_` |
| `auth_unavailable` | 503 | Auth service degraded (Supabase outage). Transient — retry with backoff. |
| `user_not_found` | 401 | Token shape OK but unknown / revoked |
| `rate_limited` | 429 | Too many requests in the current 1-minute window. See [rate limits](#rate-limits). |
| `missing_data` | 400 | Required input field absent (e.g. `at_iso` for schedule) |
| `empty_content` | 400 | Post content is empty / whitespace only |
| `post_not_found` | 404 | Post doesn't exist OR is owned by another user. Both surface as 404 to avoid leaking ownership. |
| `token_expired` | 400 | LinkedIn OAuth token (NOT your MagicPost PAT) expired. `actionable: true` — user must reconnect LinkedIn. |
| `failed_tag` | 400 | One or more `@mentions` in the post can't be tagged (LinkedIn privacy settings). |
| `too_late` | 400 | Trying to schedule more than 6 months in the future |
| `already_published` | 409 | Trying to cancel-schedule or republish a post that already went out |
| `db_error` | 500 | Internal DB issue. Detail in our logs / Slack, not exposed in response. |
| `unknown_error` | 500 | Anything we didn't anticipate. Tracked in Sentry on our side. |
| `network_error` | (varies) | The MCP server couldn't reach our API. Surfaced by the MCP tool layer, not by the API itself. |

## Schedule warnings — not errors

These are returned as HTTP **200** with `success: false`. They're informational,
not failures:

| `warning` | When |
| - | - |
| `same_day_posts` | At least one other post is scheduled the same day in the user's timezone |
| `exact_slot_conflict` | Another post is scheduled within ±5 minutes of the target slot (stronger signal) |

To proceed despite the warning, re-call with `confirm_same_day: false` (and/or
`confirm_exact_slot: false`).

## Rate limits

Per-PAT, per-minute, fixed window:

| Class | Limit | Endpoints |
| - | - | - |
| Default | **60 req / min** | Everything by default |
| Expensive | **5 req / min** | `/api/v1/refresh-posts` (hits LinkedIn) |

When you exceed the limit, you get HTTP 429 with:

```json theme={null}
{
  "error": "Rate limit exceeded (60 req/min for this endpoint class)",
  "error_type": "rate_limited",
  "current_count": 61,
  "limit": 60,
  "hint": "Wait until the start of the next minute and retry."
}
```

The window resets at the start of every wall-clock minute.

## Retry policy

| Error | Retry? |
| - | - |
| `network_error` | Yes — exponential backoff, up to 2 retries |
| 5xx (`db_error`, `unknown_error`, `auth_unavailable`) | Yes — backoff |
| `rate_limited` | Yes, after waiting until the next minute |
| 4xx (everything else) | **No** — the request is wrong, retrying won't help |

The MCP server already implements this policy internally; your tools see only
the final outcome.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.