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

# Errors

> Every error MADIAD Hub returns shares one envelope — here's the full code reference.

Every failed request returns the same JSON shape, no matter which endpoint or what went wrong:

```json theme={null}
{
  "error": {
    "code": "invalid_request",
    "message": "`platforms` is required (array or comma-separated)."
  }
}
```

`code` is a stable, machine-readable string you can branch on. `message` is a human-readable description — useful for logs, but don't parse it; it can change wording over time.

When a target platform refuses a post, `message` begins with **that platform's own wording, unchanged** — usually in English. Where MADIAD Hub has a Vietnamese rendering of it, that follows after `· Tạm dịch:`. The translation is a translation: it carries the platform's clauses and its numbers, and adds no advice of ours. A message with no rendering stays English rather than being guessed at. The only other edits are to our own identifiers: the publishing backend's name, support address and docs links are replaced with MADIAD's, and internal profile identifiers are removed.

## Error codes

| `code`                    | HTTP status      | When it happens                                                                                                                                                                                                                                                                     | How to handle                                                                                                                                                                                                                                                                                            |
| ------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unauthorized`            | 401              | Missing, malformed, or invalid API key (or an expired dashboard session on browser-session routes).                                                                                                                                                                                 | Check the `Authorization: Bearer` header. Re-issue or rotate the key from the dashboard. Not retryable without fixing credentials.                                                                                                                                                                       |
| `forbidden`               | 403              | The account is valid but not allowed to do this — an `expired` or `banned` account, or a feature (like DM/engagement automation) not included in the current plan.                                                                                                                  | Don't retry as-is. Check account status or upgrade the plan.                                                                                                                                                                                                                                             |
| `not_found`               | 404              | The route doesn't exist, or a referenced resource (profile, etc.) doesn't belong to your account.                                                                                                                                                                                   | Check the URL and IDs. Not retryable without changing the request.                                                                                                                                                                                                                                       |
| `invalid_request`         | 400              | The request failed validation, either here or at the target platform — a required field is missing, a caption or title is over a platform's character limit, an unsupported platform was listed, a URL isn't a valid `https://` link, the platform rejected the content.            | Fix the request per `message`. Not retryable without changing the payload.                                                                                                                                                                                                                               |
| `quota_exceeded`          | 429              | A metered allowance is exhausted: monthly FFmpeg minutes or the daily DM cap. Publishing has no monthly cap on a paid plan, so on a publish call this only means the account has **no active plan** at all.                                                                         | Not retryable until the allowance resets or the plan is activated/upgraded. Check [Usage](/concepts/usage) and surface the message to the user rather than looping retries.                                                                                                                              |
| `no_active_plan`          | 403              | You're trying to connect a social account (mint a connect link) but the account has no active plan (zero profile allowance).                                                                                                                                                        | Activate a plan from the dashboard billing page first. Not retryable as-is.                                                                                                                                                                                                                              |
| `profile_limit_reached`   | 403              | Creating a new profile would exceed the current plan's profile limit.                                                                                                                                                                                                               | Upgrade the plan or delete an unused profile, then retry.                                                                                                                                                                                                                                                |
| `rate_limited`            | 429              | You've exceeded the per-minute request rate limit (separate from the metered allowances — see [Rate limits](/concepts/rate-limits)), **or** the publishing backend rate-limited the call.                                                                                           | Back off and honour the `Retry-After` header (seconds) before retrying.                                                                                                                                                                                                                                  |
| `idempotency_conflict`    | 409              | The `Idempotency-Key` you sent was already used for a request with **different** parameters.                                                                                                                                                                                        | Use a new key for a genuinely new request, or confirm the payload matches the original attempt. Not safely retryable with the same key + different body.                                                                                                                                                 |
| `idempotency_in_progress` | 409              | A request with this `Idempotency-Key` is still being processed (a concurrent or overlapping retry landed while the first attempt hadn't finished).                                                                                                                                  | Wait briefly and retry the same request with the same key — it will pick up the stored result once the first attempt completes.                                                                                                                                                                          |
| `platform_not_startable`  | 400              | The platform is supported for publishing but cannot be started from the per-platform connect endpoint (Bluesky, which uses an app password rather than OAuth).                                                                                                                      | Use the shared connect link from [`POST /v1/connections/start`](/concepts/profiles) for this platform instead.                                                                                                                                                                                           |
| `link_not_found`          | 404              | A connect-link token that does not exist or has expired (links live 48 hours). One code covers both, so a token cannot be probed for existence.                                                                                                                                     | Mint a fresh link with `POST /v1/connections/start`.                                                                                                                                                                                                                                                     |
| `platform_not_connected`  | 409              | The Profile has no account connected for a platform the request targeted, so the post can never go out as sent.                                                                                                                                                                     | Connect the account under **Kết nối** in the dashboard, or drop that platform from `platforms`. Retrying the same request unchanged cannot succeed. If it keeps happening right after each successful post, the platform is restricting the account and reconnecting will not lift it — see the message. |
| `upstream_error`          | 502              | A genuine failure of the publishing backend: a network error, or a 5xx from it. Content and permission problems are **not** in here — those are `400`/`409`.                                                                                                                        | Transient. Safe to retry the whole request **only if you sent an `Idempotency-Key`**, so a retry can't duplicate a post.                                                                                                                                                                                 |
| `upstream_timeout`        | 502              | The publish took longer than MADIAD Hub waits, so **the outcome is unknown** — the post may already be live. Nothing was refunded and the `Idempotency-Key` you sent is closed against this answer, so replaying it returns this same verdict rather than publishing a second copy. | Do **not** retry blindly. Check [history](/concepts/posts) or the platform itself first; if you decide to send it again, use a NEW `Idempotency-Key`.                                                                                                                                                    |
| `internal_error`          | 500 (rarely 502) | An unhandled error inside MADIAD Hub (database error, unexpected exception). Rare, and not something you caused. A few operations may return 502 instead of 500.                                                                                                                    | Treat both as retryable — retry with backoff. If it persists, contact support with the request details.                                                                                                                                                                                                  |

<Tip>
  See [Idempotency](/concepts/idempotency) for how `Idempotency-Key` prevents duplicate posts on retry, and [Rate limits](/concepts/rate-limits) for the per-minute limit behind `rate_limited`.
</Tip>

## Should you retry?

Branch on the **status code**, never on the wording of `message`:

| Status | Meaning                                                        | Retry the same request?                                                                                                                                                                       |
| ------ | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `4xx`  | The request, the content, or the account state is the problem. | **No.** Change something first — the same request will fail the same way.                                                                                                                     |
| `429`  | Too fast, or an allowance is exhausted.                        | Yes, after the `Retry-After` delay.                                                                                                                                                           |
| `5xx`  | Our side or the publishing backend failed.                     | Yes, with backoff — and send an `Idempotency-Key` so a retry can't duplicate a post. The one exception is `upstream_timeout`, which means the outcome is unknown: check before sending again. |

<Warning>
  Until 2026-08-07 every failure coming from the publishing backend was reported as `502`, including permanent ones like a caption over the limit or a Profile with no connected account. Automations that retry on `5xx` therefore retried requests that could never succeed. If your integration works around that by parsing `message`, you can now delete that code and branch on the status.
</Warning>

## Errors without the standard envelope

A very large multipart upload can be rejected at the network edge **before** it reaches MADIAD Hub — the response is a plain `413 Request Entity Too Large` (no JSON envelope). Send the images as [`photo_urls[]`](/concepts/posts#send-photos-by-url) instead: MADIAD Hub downloads them server-side, so the request body stays small no matter how big the images are. Compressing the files also works for smaller overruns.
