ReelMoney API

Built for agents

Generate slideshows and videos, publish them to TikTok, YouTube, Instagram and LinkedIn, and read the numbers back. Over REST, or over MCP.

Authentication

Every request to the REST API and to the MCP server carries an API key as a bearer token.

curl https://reel.money/api/v1/account \
  -H "Authorization: Bearer rm_your_key"

A key is created in ReelMoney under Settings, then API keys, at https://reel.money/brands/<brand_id>/api_keys. The token is shown once, at creation. If it is lost, revoke it and make another one.

Facts worth knowing before you build:

  • A key belongs to one brand and acts on that brand by default. Only a key carrying the brands:all scope can reach the account's other brands, per request, with brand_id.
  • Plan limits, quota and credits are the brand owner's. GET /api/v1/account reports them.
  • A brand can hold up to ten keys, so give each agent its own and revoke the one that misbehaves.
  • Keys can carry an expiry date. An expired key answers key_expired, a revoked key answers key_revoked.
  • No CORS headers are sent, on purpose. Call the API from a server or an agent runtime, never from a browser page where the key would be readable.

Scopes

A key carries one or more scopes, ticked when it is created. A scope it does not carry is a wall, not a warning.

ScopeWhat it allows
brand:readRead your brand profile, plan, quota, videos and slideshows.
media:readBrowse your media library, media packs and stock media.
media:writeUpload files and add them to your media library.
content:writeCreate and edit slideshows and videos, and start renders.
posts:readRead your posts and scheduled posts, and run post checks.
posts:writePublish and schedule posts, and cancel scheduled posts.
analytics:readRead post metrics and account analytics.
brands:allWork across every brand this account owns, choosing the brand per request.

Publishing is deliberately separate. A key with content:write but no posts:write can research, generate and render all day and still cannot put anything in front of an audience. That is the setting to start an agent on.

Each endpoint names the scope it needs in /openapi.json as x-required-scope, and the endpoint reference repeats it per group.

One key, many brands

brands:all is the opt in exception to the one key, one brand rule. A key that carries it may act on any brand the same account owns: send the optional brand_id query parameter (or the X-Brand-Id header, the parameter winning) on any brand facing endpoint, with an id from GET /api/v1/brands. Leave brand_id out and the key works on its own brand, exactly as every key always has, so nothing changes for an existing integration. Without the scope, a brand_id naming a different brand answers 403 missing_scope with brands:all in details.required_scope, and an id the account does not own answers 404 not_found, whatever the scopes. Quota, credits and rate limits stay the key owner's in every case.

Keys made before this vocabulary

Older keys carry read, write or admin. They keep working exactly as they did and are read like this:

Old scopeReads as
readbrand:read, media:read, posts:read, analytics:read
writecontent:write, media:write, posts:write
adminbrand:read, media:read, media:write, content:write, posts:read, posts:write, analytics:read, brands:all

A key with no scopes at all is treated as read. It can never write. Edit such a key in the dashboard to tick the permissions you want instead.

What a failure looks like

A missing or bad token answers 401 with an RFC 6750 challenge header, which is what an OAuth capable client looks for:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="ReelMoney", error="invalid_token"
{
  "success": false,
  "errors": [
    { "code": "invalid_token", "message": "The API key is not valid.", "retryable": false }
  ]
}

A valid key without the right scope answers 403 and names the scope it wanted, so an agent can tell a person exactly which box to tick:

{
  "success": false,
  "errors": [
    {
      "code": "missing_scope",
      "message": "The API key does not have the permission this endpoint requires.",
      "retryable": false,
      "details": {
        "required_scope": "posts:write",
        "key_scopes": ["brand:read", "content:write"]
      }
    }
  ]
}

Branch on code, never on the message text. Messages are written for people and may be reworded. Codes are a contract. See the error reference.

Response shape

Every success looks like this:

{ "success": true, "data": { }, "meta": { } }

Every failure looks like this:

{ "success": false, "errors": [{ "code": "quota_exceeded", "message": "...", "retryable": false }] }

List endpoints take page and per_page (1 to 50, 25 by default) and report meta.pagination with page, per_page, total_pages and total_count.

Rate limits

Ceilings are counted per key, not per account, so one noisy agent cannot starve another.

BucketCeiling per key
Every endpoint300 requests per 5 minutes
Generation (create slideshow, render slideshow, create video)20 requests per 1 hour
Posting (publish or schedule)30 requests per 1 hour

Every response carries the current bucket in headers:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Window: 300

Over a ceiling the API answers 429 rate_limited with a Retry-After header and details.retry_after_seconds. Wait that long, then continue. Do not spin.

Social platforms enforce their own separate caps. Those surface as platform_rate_limited, and POST /api/v1/posts/validate reports the headroom left per account before you spend it.

Idempotency keys

The create endpoints (POST /api/v1/slideshows, POST /api/v1/slideshows/{id}/render, POST /api/v1/videos, POST /api/v1/posts) accept an Idempotency-Key header. Send a fresh unique value, such as a UUID, per logical action.

curl -X POST https://reel.money/api/v1/slideshows \
  -H "Authorization: Bearer rm_your_key" \
  -H "Idempotency-Key: 5a1f9b2c-7f1e-4c73-9c6d-1b2f3a4d5e6f" \
  -H "Content-Type: application/json" \
  -d '{ "context": "..." }'
  • Same key, same body: the stored response is replayed with meta.idempotent_replay set to true. Nothing runs twice.
  • Same key, different body: 409 idempotency_conflict. One key belongs to one request forever.
  • Same key while the first request is still running: 409 idempotency_conflict with details.in_flight.
  • No header: nothing is stored and the request behaves normally.

Keys are scoped to the API key and the exact path, and they are kept for 24 hours. This is what makes a timeout safe to retry: send the same key again and you get the first answer instead of a second slideshow.

Housekeeping

  • Rotate a key by creating the new one, moving the agent over, then revoking the old one.
  • Revoking is instant. last_used_at and the request count on each key show what an agent has actually been doing.
  • If a key leaks, revoke it first and ask questions after. There is no way to unpublish a post that has already gone out.

Reading this as an agent? Every page is also plain markdown: /api-docs/authentication.md