# Errors

Every failure answers with the same envelope and a stable machine code.

```json
{
  "success": false,
  "errors": [
    {
      "code": "quota_exceeded",
      "message": "The plan quota for this action is used up.",
      "retryable": false,
      "details": { "slideshows_remaining": 0 }
    }
  ]
}
```

Branch on `code`. Messages are written for people and may be reworded; codes are the contract. `retryable` says whether sending the same request again could ever work: false means fix something first, true means the failure was about timing or load.

`details` appears when there is something concrete to hand back, such as `required_scope` on a permission failure, `fields` on a validation failure or `retry_after_seconds` on a rate limit.

Two ways to look a code up at runtime, so an agent never has to guess:

```bash
curl https://reel.money/api/v1/errors/quota_exceeded \
  -H "Authorization: Bearer rm_your_key"
```

Over MCP, the same lookup is the `explain_error` tool.

## HTTP status, in short

| Status | Means |
| --- | --- |
| 400 | The request could not be read. |
| 401 | No key, or a key that is not valid, revoked or expired. |
| 402 | Not enough credits. |
| 403 | The key lacks a scope, or the plan quota is used up. |
| 404 | No such record, or not one this key can see. |
| 409 | The record is busy or has moved past the point where this action applies. |
| 415 | That file type is not supported. |
| 422 | The body did not pass validation, or a platform rule blocks it. |
| 429 | Too many requests, from this key or towards a social platform. |
| 5xx | Our side. Try again in a moment. |

## Every code

This table is generated from the same catalog the API answers with, so it is never out of step with what you will actually receive.

| Code | Message | Cause | Fix | Retry helps |
| --- | --- | --- | --- | --- |
| `missing_token` | No API key was sent with the request. | The Authorization header was missing or did not contain a bearer token. | Send the header Authorization: Bearer rm_your_key with every request. | no |
| `invalid_token` | The API key is not valid. | No active key matches the token that was sent. | Check the key was copied in full, then create a new key in ReelMoney under Settings and API keys. | no |
| `key_revoked` | This API key was revoked. | Someone revoked the key in the ReelMoney dashboard. | Create a new key in ReelMoney under Settings and API keys, then use that token. | no |
| `key_expired` | This API key has expired. | The key passed the expiry date chosen when it was created. | Create a new key in ReelMoney under Settings and API keys, then use that token. | no |
| `missing_scope` | The API key does not have the permission this endpoint requires. | The key was created without the scope listed in details.required_scope. | Edit the key in ReelMoney and tick the missing permission, or create a new key with it. | no |
| `approval_required` | This action needs a person to approve it first. | The action is gated behind human approval. | Ask the account owner to approve the action, then send the request again. | no |
| `quota_exceeded` | The plan quota for this action is used up. | The account reached its limit for the current billing period or the free tier limit. | Wait for the quota to reset, or upgrade the plan. Call GET /api/v1/account to see what is left. | no |
| `insufficient_credits` | The account does not have enough credits for this action. | AI image generation and some renders spend credits, and the balance is too low. | Buy credits in ReelMoney, or choose an image source of pack or stock instead of ai. | no |
| `validation_failed` | The request body did not pass validation. | One or more fields are missing or hold a value the endpoint does not accept. | Read details.fields for the exact problems, correct the body and send the request again. | no |
| `bad_request` | The request could not be read. | A required parameter was missing, or the body was not valid JSON. | Check the endpoint reference in the OpenAPI document, then send a corrected request. | no |
| `not_found` | That record does not exist, or this key cannot see it. | The id belongs to another brand, or the record was deleted. | List the records first and use an id from that response. | no |
| `unknown_error_code` | That error code is not in the catalog. | GET /api/v1/errors/:code was called with a code ReelMoney does not publish. | Read details.known_codes for the full list, then look up one of those codes. | no |
| `job_not_found` | That job does not exist for this brand. | The job id was wrong, or the job belongs to another brand. | Use the job id returned by the create call that started the work. | no |
| `conflict` | The record is in a state that does not allow this action. | Work is already running on it, or it has moved past the point where this action applies. | Poll the job or the record until it settles, then decide what to do. | yes |
| `job_in_progress` | A job is still running on this record. | The record is being generated or rendered right now, so it cannot be changed. | Poll GET /api/v1/jobs/:id until the job settles, then send the request again. | yes |
| `generation_failed` | The generation job failed. | The job hit an error while generating this content. | Read the error message, adjust the request if needed and start a new generation. | yes |
| `idempotency_conflict` | This Idempotency-Key was already used with a different request body. | An Idempotency-Key may only ever be paired with one request body. | Send the original body again to replay the stored response, or use a fresh Idempotency-Key. | no |
| `media_too_large` | The file is larger than the limit for its type. | Images may be up to 10 MB and videos up to 200 MB. | Compress the file or shorten the video, then upload it again. | no |
| `unsupported_media_type` | That file type is not supported. | The content type is outside the allowed list for images and videos. | Convert the file to jpeg, png, webp or mp4 and upload it again. | no |
| `platform_not_connected` | No connected account was found for that platform. | The brand has never connected the platform, or the connection was removed. | Call POST /api/v1/connections/link, open the returned URL in a browser and finish the connect flow. | no |
| `token_expired` | The connection to that platform has expired. | The platform access token expired or was revoked by the platform. | Reconnect the account with POST /api/v1/connections/link and open the returned URL. | no |
| `platform_rate_limited` | The social platform is refusing more posts right now. | The platform posting cap for this account was reached. TikTok allows 15 posts per 24 hours. | Schedule the post for later, or post to another connected account. | yes |
| `unsupported_platform` | That platform is not one ReelMoney posts to. | The platform name was not tiktok, youtube, instagram or linkedin. | Send one of tiktok, youtube, instagram or linkedin. Call GET /api/v1/capabilities for the list. | no |
| `platform_not_configured` | This ReelMoney server cannot start a connect flow for that platform. | The client id or the callback URL for the platform is missing from the server settings. | Connect the account from the ReelMoney dashboard instead, and tell support the platform is unset. | no |
| `short_not_postable` | That short has nothing to publish yet. | The short has no rendered video and no slides, so there is no file to send to a platform. | Poll GET /api/v1/jobs/:id until the short succeeds, then post it. | yes |
| `caption_too_long` | The caption is longer than the platform accepts. | Each platform caps caption length. TikTok allows 4000 characters and Instagram allows 2200. | Shorten the caption, or send a shorter one for this account in platform_customizations. | no |
| `title_too_long` | The title is longer than the platform accepts. | Each platform caps title length. TikTok allows 90 characters and YouTube allows 100. | Shorten the title, or send a shorter one for this account in platform_customizations. | no |
| `schedule_too_soon` | That scheduled time is in the past or too close to now. | A scheduled post needs at least 5 minutes of lead time so the job can be queued. | Send scheduled_at as an ISO 8601 time at least 5 minutes ahead, or omit it to post right away. | no |
| `not_cancellable` | That post can no longer be cancelled. | Only posts still sitting in draft, pending or scheduled can be pulled back. | Read the post status with GET /api/v1/posts and cancel only the ones that have not gone out. | no |
| `rate_limited` | This API key sent too many requests. | The key passed one of the request ceilings for the current window. | Wait for details.retry_after_seconds, then send the request again. Spread bursts over time. | yes |
| `internal_error` | Something went wrong on our side. | The request reached ReelMoney but could not be completed. | Send the request again in a moment. If it keeps failing, contact support with the request id. | yes |

## Handling them well

- **`quota_exceeded` and `insufficient_credits` are not retry material.** Call `GET /api/v1/account` before a run and stop early rather than failing halfway.
- **`missing_scope` is a message for a person.** Relay `details.required_scope` and the fix text: someone has to tick a box on the key.
- **`rate_limited` carries `Retry-After`.** Honour it. Retrying sooner just spends the next window.
- **`platform_rate_limited` is the platform's cap, not ours.** Schedule the post for later or send it to another account.
- **`token_expired` and `platform_not_connected` both end at the same place:** build a connect link with `POST /api/v1/connections/link` and give it to a person.
- **`job_in_progress` and `conflict` mean wait, then look again.** Poll the job rather than retrying the edit in a tight loop.
- **`idempotency_conflict` means the key was reused.** Send the original body to replay the first answer, or use a fresh key for a genuinely new request.
