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.
| Group | Endpoints | Scope |
|---|---|---|
| Discovery | account, brand, brands, capabilities, errors | brand:read |
| Media | direct_uploads, media, media_packs, background_music | media:read, media:write |
| Generation | slideshows, videos, render | content:write |
| Jobs and shorts | jobs, shorts | brand:read |
| Connections | connections, connections/link | brand:read |
| Posting | posts, posts/validate | posts:read, posts:write |
| Analytics | analytics, analytics accounts, refresh | analytics:read |
Discovery
Read these before you build a request. They are cheap and they stop most mistakes.
| Endpoint | What it answers |
|---|---|
GET /api/v1/account | Who 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/brand | The brand profile (name, description, website, language, categories), the slideshow settings that shape every generation, and which platforms are connected. |
GET /api/v1/brands | The brands the key owner belongs to, with counts. Generation always happens in the key's own brand. |
GET /api/v1/capabilities | The 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.
| Key | Name | Look |
|---|---|---|
classic_stroke | Classic TikTok stroke | White lowercase text with a black outline, the native TikTok caption look. |
clean_pills | Clean pill stack | Short lines inside crisp white pills over soft photos, polished and easy to save. |
bold_statement | Bold statement caps | Huge condensed capitals that read like a motivational poster. |
editorial_serif | Editorial serif | Elegant serif headlines with a small caption low on the frame, quiet magazine feel. |
handwritten_diary | Handwritten diary | Everything in a handwriting font, like a page torn out of a personal journal. |
typewriter_notes | Typewriter notes | Typewriter text anchored high on the slide, like the first lines of a private note. |
accent_pop | Accent color pop | One loud accent color on the key lines, plain white text everywhere else. |
black_card | Black card | Near black pills with white text, sleek and declarative. |
whisper_minimal | Whisper minimal | One tiny caption low on the frame so the photo carries the slide. |
serif_quote_cards | Serif quote cards | Every slide is a centered serif quote, made to be reposted to a story. |
numbered_bold | Numbered bold list | Giant list numbers as the design, with one tight point under each. |
duo_serif_sans | Serif + sans duo | An 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.
| Key | Name | Imagery |
|---|---|---|
pinterest_girl | Pinterest lifestyle (faceless woman) | Lifestyle photos of a woman, always faceless or from behind, in soft natural light. |
cozy_interior | Cozy interiors | Warm interiors with candles, linen beds, steaming mugs and rain on the window. |
moody_luxury | Moody luxury | Dark luxury still life with black marble, watches, leather and city lights at night. |
nature_escape | Nature escape | Big calm landscapes: misty mountains, ocean cliffs, forest light and alpine lakes. |
city_nights | City nights | Night streets with neon reflections, wet asphalt, light trails and rain bokeh. |
minimal_flatlay | Minimal objects & flatlays | Clean objects and flatlays on plain backgrounds with lots of empty space for text. |
gym_discipline | Gym & discipline | Gritty training photos: dark gyms, chalk dust, early runs, faceless athletes. |
coastal_summer | Coastal summer | Sun 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:
- Presign.
POST /api/v1/direct_uploadswithfilename,byte_size,content_typeand, when you have it, a base64 MD5checksum. Answers 201 with asigned_idand adirect_uploadobject holding aurland the exactheadersto use. Scopemedia:write. - Upload.
PUTthe raw bytes to thaturlwith those headers, exactly as given. This request goes to storage, not to ReelMoney, and carries no API key. - Register.
POST /api/v1/mediawith{"file": "<signed_id>"}plus an optionaltitle,media_type(user_uploadorproduct_demo) andtags. Answers 201 with the media record. Scopemedia: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:
| Endpoint | What it lists |
|---|---|
GET /api/v1/media?source=own | The media this brand owns. query matches title and tags. |
GET /api/v1/media?source=packs | The media inside the packs the owner can reach. |
GET /api/v1/media?source=stock | The 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_video | The avatar clips a hook and demo video can use. |
GET /api/v1/media_packs | The packs this account can use, with a preview. Pass an id as image_source.media_pack_id. |
GET /api/v1/background_music | The 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.
| Field | Notes |
|---|---|
context | Required, up to 3500 characters. Say who it is for and what it should get across. The single biggest lever on quality. |
slide_count | 0 lets the model choose the length, otherwise send 3 to 12. |
aspect_ratio | One of the ratios above. Defaults to 9:16. |
image_source | {"type": "stock"}, {"type": "pack", "media_pack_id": 12} or {"type": "ai"}. |
design_style | A key from the design styles table. Leave it out for variety. |
title, caption, tags, language | Optional. 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.
| Field | Notes |
|---|---|
hook_text | The line the avatar speaks and shows on screen. |
avatar_media_id or avatar_url | Required. Browse GET /api/v1/media?source=stock&media_type=avatar_video, or upload your own clip. |
product_demo_media_ids, product_demo_urls | The demo clips, played in the order given. |
background_music_id | An id from GET /api/v1/background_music. |
text_settings, loop_count, title, caption | Optional. |
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:
statusis one ofqueued,processing,succeeded,failed. Nothing else.- Wait
poll_after_secondsbetween polls. It is repeated in theRetry-Afterheader. Typical intervals:slideshow_generationevery 15 seconds,video_generationevery 30 seconds,renderevery 30 seconds. - Stop the moment the status reads
succeededorfailed. A finished job does not change again. - A failed job carries
errorwith a code from the catalog and whether a retry can help. A succeeded job carriesresultwithshort_idand anypreview_url,video_urlor slide count.
Shorts, also brand:read:
GET /api/v1/shortslists this brand's slideshows and videos, newest first, filtered byshort_type(SLIDESHOW,UGC,all),status,created_afterandcreated_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/connectionslists the accounts this brand can post to. Each one reports its token status, its platform posting headroom and a singleready_to_postflag. Platforms with no account are listed undernot_connected. Access tokens never leave the server, so no token appears in the payload.POST /api/v1/connections/linkwith{"platform": "tiktok"}(oryoutube,instagram,linkedin) answers withconnect_url, theinstructionsto relay and anexpires_note.
The steps, which the response repeats in plain words:
- Give
connect_urlto the person who owns the account. Do not try to follow it yourself. - They sign in to the platform and approve the permissions. They land back on ReelMoney with the account attached to this brand.
- Call
GET /api/v1/connectionsagain 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.
| Endpoint | Scope | What it does |
|---|---|---|
POST /api/v1/posts/validate | posts:read | Dry run. Same body as the post. Enqueues nothing, answers one verdict per account. |
POST /api/v1/posts | posts:write | Publish now, or schedule with scheduled_at. Answers 202 with a partial success report. |
GET /api/v1/posts | posts:read | Posts and scheduled posts, newest first. Filter by short_id, status, platform. |
DELETE /api/v1/posts/{id} | posts:write | Cancel 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/analyticsgives a summary per connected account.GET /api/v1/analytics/accounts/{social_data_id}gives post by post metrics plus the account summary.periodis24h,7d,30dor90d,filter=reelmoneynarrows to posts published through ReelMoney, andsortacceptsposted_ator any of the count columns withdir.POST /api/v1/analytics/accounts/{social_data_id}/refreshpulls fresh numbers and answers 202. Inside its cooldown it answers 429 with the seconds left indetails.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