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.

Endpoints

Base URL https://reel.money. Every endpoint needs Authorization: Bearer rm_.... The full contract, with every field and every example, is /openapi.json. This page is the map.

Every brand facing endpoint also takes an optional brand_id query parameter (everything except GET /api/v1/account, GET /api/v1/capabilities and GET /api/v1/errors/{code}). Omit it and the request acts on the key's own brand. A key with the brands:all scope may name any brand the account owns, ids from GET /api/v1/brands; see authentication for the rules.

GroupEndpointsScope
Discoveryaccount, brand, brands, capabilities, errorsbrand:read
Mediadirect_uploads, media, media_packs, background_musicmedia:read, media:write
Generationslideshows, videos, rendercontent:write
Jobs and shortsjobs, shortsbrand:read
Connectionsconnections, connections/linkbrand:read
Postingposts, posts/validateposts:read, posts:write
Analyticsanalytics, analytics accounts, refreshanalytics:read

Discovery

Read these before you build a request. They are cheap and they stop most mistakes.

EndpointWhat it answers
GET /api/v1/accountWho the key belongs to, the plan, the slideshow quota used and remaining, credits when the plan shows them, the brands the owner can reach, and the scopes this key holds.
GET /api/v1/brandThe brand profile (name, description, website, language, categories), the slideshow settings that shape every generation, and which platforms are connected.
GET /api/v1/brandsThe brands the key owner belongs to, with counts. Generation always happens in the key's own brand.
GET /api/v1/capabilitiesThe enums and limits the API accepts. Any valid key may call it.
GET /api/v1/errors/{code}The cause, the fix and whether a retry helps, for any code the API publishes.

GET /api/v1/capabilities is the one to read before generating. It carries the design styles, the image worlds, the overlay fonts, the aspect ratios, the slide count bounds, the short types, the image sources with whether AI images are affordable right now, the platform rules, and the poll guidance. The values come from the same code that enforces them, so it cannot lie.

Design styles

Leave design_style out of a create and ReelMoney picks a recipe the brand has not used lately, which is what keeps a feed varied. Pin one only when a caller asks for a specific look.

KeyNameLook
classic_strokeClassic TikTok strokeWhite lowercase text with a black outline, the native TikTok caption look.
clean_pillsClean pill stackShort lines inside crisp white pills over soft photos, polished and easy to save.
bold_statementBold statement capsHuge condensed capitals that read like a motivational poster.
editorial_serifEditorial serifElegant serif headlines with a small caption low on the frame, quiet magazine feel.
handwritten_diaryHandwritten diaryEverything in a handwriting font, like a page torn out of a personal journal.
typewriter_notesTypewriter notesTypewriter text anchored high on the slide, like the first lines of a private note.
accent_popAccent color popOne loud accent color on the key lines, plain white text everywhere else.
black_cardBlack cardNear black pills with white text, sleek and declarative.
whisper_minimalWhisper minimalOne tiny caption low on the frame so the photo carries the slide.
serif_quote_cardsSerif quote cardsEvery slide is a centered serif quote, made to be reposted to a story.
numbered_boldNumbered bold listGiant list numbers as the design, with one tight point under each.
duo_serif_sansSerif + sans duoAn elegant serif headline paired with a much smaller clean sans support line.

Image worlds

The photographic world a generation lands in. Chosen automatically alongside the design style.

KeyNameImagery
pinterest_girlPinterest lifestyle (faceless woman)Lifestyle photos of a woman, always faceless or from behind, in soft natural light.
cozy_interiorCozy interiorsWarm interiors with candles, linen beds, steaming mugs and rain on the window.
moody_luxuryMoody luxuryDark luxury still life with black marble, watches, leather and city lights at night.
nature_escapeNature escapeBig calm landscapes: misty mountains, ocean cliffs, forest light and alpine lakes.
city_nightsCity nightsNight streets with neon reflections, wet asphalt, light trails and rain bokeh.
minimal_flatlayMinimal objects & flatlaysClean objects and flatlays on plain backgrounds with lots of empty space for text.
gym_disciplineGym & disciplineGritty training photos: dark gyms, chalk dust, early runs, faceless athletes.
coastal_summerCoastal summerSun drenched coastal scenes with turquoise water, white sand and bright streets.

Aspect ratios

9:16 at 1080 by 1920 (default), 1:1 at 1080 by 1080, 16:9 at 1080 by 608, 4:5 at 1080 by 1350, 2:3 at 1080 by 1620

Media

Getting a file in takes three moves, in this order:

  1. Presign. POST /api/v1/direct_uploads with filename, byte_size, content_type and, when you have it, a base64 MD5 checksum. Answers 201 with a signed_id and a direct_upload object holding a url and the exact headers to use. Scope media:write.
  2. Upload. PUT the raw bytes to that url with those headers, exactly as given. This request goes to storage, not to ReelMoney, and carries no API key.
  3. Register. POST /api/v1/media with {"file": "<signed_id>"} plus an optional title, media_type (user_upload or product_demo) and tags. Answers 201 with the media record. Scope media:write.

Skip step three and the upload is eventually swept away, because a blob with no owning record is not media yet.

Limits: images up to 10 MB, videos up to 200 MB. Accepted types are jpeg, png, webp, gif, heic, heif, mp4, webm and quicktime. A file over the cap answers media_too_large, a type outside the list answers unsupported_media_type.

curl -X POST https://reel.money/api/v1/direct_uploads \
  -H "Authorization: Bearer rm_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "filename": "kitchen-b-roll.mp4", "byte_size": 8412332, "content_type": "video/mp4" }'

Reading media, all under scope media:read:

EndpointWhat it lists
GET /api/v1/media?source=ownThe media this brand owns. query matches title and tags.
GET /api/v1/media?source=packsThe media inside the packs the owner can reach.
GET /api/v1/media?source=stockThe shared global library. Here query is a meaning based image search, so describe the picture rather than guessing keywords.
GET /api/v1/media?source=stock&media_type=avatar_videoThe avatar clips a hook and demo video can use.
GET /api/v1/media_packsThe packs this account can use, with a preview. Pass an id as image_source.media_pack_id.
GET /api/v1/background_musicThe tracks a video can name through background_music_id.

Generation

Every create answers 202 with a job envelope, never with finished content, and every create takes an Idempotency-Key header. All of them need content:write.

POST /api/v1/slideshows

Writes the slides, art directs them and sources an image for each one.

FieldNotes
contextRequired, up to 3500 characters. Say who it is for and what it should get across. The single biggest lever on quality.
slide_count0 lets the model choose the length, otherwise send 3 to 12.
aspect_ratioOne of the ratios above. Defaults to 9:16.
image_source{"type": "stock"}, {"type": "pack", "media_pack_id": 12} or {"type": "ai"}.
design_styleA key from the design styles table. Leave it out for variety.
title, caption, tags, languageOptional. tags is a comma separated string.

image_source.type of ai generates a fresh image per slide and spends credits. Without enough credits it answers 402 insufficient_credits, and stock or pack cost nothing. Over the plan's monthly slideshow cap it answers 403 quota_exceeded, which GET /api/v1/account would have told you first.

PATCH /api/v1/slideshows/{id}

Edit a generated slideshow: title, caption, tags, aspect_ratio, gradient settings, or the slides themselves. Submitted text overlays are normalised server side the same way generated ones are, so fonts, sizes, positions and contrast stay inside what the renderer supports. PUT behaves the same for clients that cannot send PATCH.

Only a settled slideshow may be edited. While a generation or a render is running it answers 409 job_in_progress.

POST /api/v1/slideshows/{id}/render

{"format": "video"} turns a slideshow into an MP4. Use this when a caller wants a video of a slideshow they already have, instead of generating a second one. Answers a job of kind render; the file is ready when the job succeeds and result.video_url is filled in.

A slideshow that already has a video, or one with a render in flight, answers 409. Posting a slideshow to TikTok as a photo post does not need a render.

POST /api/v1/videos

A hook and demo video: an avatar clip speaks the hook, then the product demo clips play.

FieldNotes
hook_textThe line the avatar speaks and shows on screen.
avatar_media_id or avatar_urlRequired. Browse GET /api/v1/media?source=stock&media_type=avatar_video, or upload your own clip.
product_demo_media_ids, product_demo_urlsThe demo clips, played in the order given.
background_music_idAn id from GET /api/v1/background_music.
text_settings, loop_count, title, captionOptional.

An avatar is required and is checked before anything is queued, so a missing one answers 422 validation_failed instead of rendering a black video half an hour later.

Jobs and shorts

GET /api/v1/jobs/{id} is the one poller for every kind of long running work, under scope brand:read. The job id is the short id the create returned.

{
  "job_id": 8842,
  "kind": "slideshow_generation",
  "status": "processing",
  "status_message": "Generating the slideshow content.",
  "poll_after_seconds": 15,
  "estimated_completion_at": "2026-08-18T09:16:02Z"
}

The contract, in four lines:

  • status is one of queued, processing, succeeded, failed. Nothing else.
  • Wait poll_after_seconds between polls. It is repeated in the Retry-After header. Typical intervals: slideshow_generation every 15 seconds, video_generation every 30 seconds, render every 30 seconds.
  • Stop the moment the status reads succeeded or failed. A finished job does not change again.
  • A failed job carries error with a code from the catalog and whether a retry can help. A succeeded job carries result with short_id and any preview_url, video_url or slide count.

Shorts, also brand:read:

  • GET /api/v1/shorts lists this brand's slideshows and videos, newest first, filtered by short_type (SLIDESHOW, UGC, all), status, created_after and created_before.
  • GET /api/v1/shorts/{id} returns one in full: the type data with the slides and overlays, the URLs, the thumbnail and every post made from it.

Connections

Connecting a social account needs a person at a browser. The API hands over the link and the steps rather than pretending otherwise. Both endpoints need brand:read.

  • GET /api/v1/connections lists the accounts this brand can post to. Each one reports its token status, its platform posting headroom and a single ready_to_post flag. Platforms with no account are listed under not_connected. Access tokens never leave the server, so no token appears in the payload.
  • POST /api/v1/connections/link with {"platform": "tiktok"} (or youtube, instagram, linkedin) answers with connect_url, the instructions to relay and an expires_note.

The steps, which the response repeats in plain words:

  1. Give connect_url to the person who owns the account. Do not try to follow it yourself.
  2. They sign in to the platform and approve the permissions. They land back on ReelMoney with the account attached to this brand.
  3. Call GET /api/v1/connections again and confirm the account shows up as active.

Authorize links go stale after a few minutes, so build one when the person is ready to click it. A platform this deployment has no client id for answers 503 platform_not_configured.

Posting

Covered in full on the posting page, including the platform rules and the partial success model.

EndpointScopeWhat it does
POST /api/v1/posts/validateposts:readDry run. Same body as the post. Enqueues nothing, answers one verdict per account.
POST /api/v1/postsposts:writePublish now, or schedule with scheduled_at. Answers 202 with a partial success report.
GET /api/v1/postsposts:readPosts and scheduled posts, newest first. Filter by short_id, status, platform.
DELETE /api/v1/posts/{id}posts:writeCancel a post that has not gone out. Anything already published answers 409 not_cancellable.

Analytics

All three need analytics:read. TikTok is the platform with an analytics adapter today, and supported_platforms in the payload says so. Accounts on other platforms are simply not listed.

  • GET /api/v1/analytics gives a summary per connected account.
  • GET /api/v1/analytics/accounts/{social_data_id} gives post by post metrics plus the account summary. period is 24h, 7d, 30d or 90d, filter=reelmoney narrows to posts published through ReelMoney, and sort accepts posted_at or any of the count columns with dir.
  • POST /api/v1/analytics/accounts/{social_data_id}/refresh pulls fresh numbers and answers 202. Inside its cooldown it answers 429 with the seconds left in details.retry_after_seconds. That is a platform friendliness limit, separate from the key ceiling.

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