{"openapi":"3.1.0","info":{"title":"ReelMoney Agent API","version":"1.0.0","summary":"Create slideshows and videos, publish them to social accounts, and read how they performed.","description":"The ReelMoney Agent API lets a program do what a person does in the ReelMoney\ndashboard: read a brand and its plan, browse and upload media, generate an AI\nslideshow or a hook and demo video, publish or schedule that content to\nconnected social accounts, and read the numbers afterwards.\n\n## Authentication\n\nEvery request carries an API key created in ReelMoney under Settings and API\nkeys. Send it as a bearer token:\n\n    Authorization: Bearer rm_your_key_here\n\nA key belongs to one brand and acts on that brand by default. Plan limits,\nquotas and credits are those of the brand owner. A key with the wrong\npermission gets a 403 with the code `missing_scope` and the scope it needed\nin `details.required_scope`.\n\nA key that carries the `brands:all` scope may act on any brand the same\naccount owns: pass the optional `brand_id` query parameter (or the\n`X-Brand-Id` header, the parameter winning) with an id from\n`GET /api/v1/brands`. Without the scope, a `brand_id` naming a different\nbrand answers 403 `missing_scope`, and an id the account does not own\nanswers 404 `not_found`. Omitting `brand_id` always means the key's own\nbrand, so existing integrations behave exactly as before.\n\n## Scopes\n\nEach key carries one or more scopes. Every operation below names the scope it\nneeds in the `x-required-scope` extension. A null value there means any valid\nkey may call it.\n\n  * `brand:read` reads the brand profile, plan, quota, shorts and jobs.\n  * `media:read` browses the media library, media packs and stock media.\n  * `media:write` uploads files and registers them as media.\n  * `content:write` creates and edits slideshows and videos, and starts renders.\n  * `posts:read` reads posts and runs post checks.\n  * `posts:write` publishes, schedules and cancels posts.\n  * `analytics:read` reads account and post metrics.\n  * `brands:all` works across every brand the account owns, choosing the\n    brand per request with `brand_id`. Off by default.\n\nKeys created before this vocabulary existed carry `read`, `write` or `admin`\nand keep working: `read` grants every read scope, `write` grants\n`content:write`, `media:write` and `posts:write`, and `admin` grants all of\nthem.\n\n## Response shape\n\nEvery success answers with `{\"success\": true, \"data\": ..., \"meta\": {...}}`.\nEvery failure answers with\n`{\"success\": false, \"errors\": [{\"code\": ..., \"message\": ..., \"retryable\": ...}]}`.\nBranch on `code`, never on the message text. Call `GET /api/v1/errors/{code}`\nfor the cause and the fix behind any code.\n\n## Long running work\n\nCreating a slideshow or a video answers 202 with a job envelope, not the\nfinished content. The job id is the short id. Poll `GET /api/v1/jobs/{job_id}`\nand wait `poll_after_seconds` between polls, which is also sent back as the\n`Retry-After` header. Stop when status reads `succeeded` or `failed`.\n\n## Idempotency\n\nThe create operations accept an `Idempotency-Key` header. Sending the same key\nwith the same body replays the stored response with\n`meta.idempotent_replay` set to true. Sending the same key with a different\nbody answers 409 `idempotency_conflict`. Retry a timed out create with the\nsame key and you will never get a second slideshow by accident.\n\n## Rate limits\n\nCeilings are per API key. Every response carries `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` and `X-RateLimit-Window`.\n\n  * 300 requests per 5 minutes across the whole API.\n  * 20 requests per hour to the generation operations (create slideshow,\n    render slideshow, create video).\n  * 30 requests per hour to the posting operation.\n\nOver a ceiling the API answers 429 `rate_limited` with a `Retry-After` header\nand `details.retry_after_seconds`. Social platforms have their own caps, which\nsurface as `platform_rate_limited`.\n\n## Pagination\n\nList operations take `page` and `per_page` (1 to 50, default 25) and report\n`meta.pagination`.\n\n## Notes\n\nBrowser calls are not supported. There is no CORS header on these endpoints,\nso call the API from a server or an agent runtime, never from a page.\n","contact":{"name":"ReelMoney","url":"https://reel.money/api-docs","email":"hello@reel.money"}},"externalDocs":{"description":"Agent guide, quickstart, error reference and MCP setup","url":"https://reel.money/api-docs"},"servers":[{"url":"https://reel.money","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Discovery","description":"Who the key belongs to, what the plan allows and which values the API accepts."},{"name":"Media","description":"Upload files, register them as media, and browse media, packs and stock."},{"name":"Generation","description":"Create slideshows and videos, edit a slideshow, and render one to video."},{"name":"Jobs","description":"Poll long running generation and render work."},{"name":"Content","description":"Read the shorts a brand owns."},{"name":"Connections","description":"Connected social accounts and the link a person opens to add one."},{"name":"Posting","description":"Check, publish, schedule, list and cancel posts."},{"name":"Analytics","description":"Account summaries and post metrics."}],"paths":{"/api/v1/account":{"get":{"tags":["Discovery"],"operationId":"getAccount","summary":"Read the account behind the key","description":"The first call an agent should make. Returns the masked owner email, the\nplan name, the plan capabilities including slideshow quota used and\nremaining, the brands the owner can reach, and the scopes this key holds.\nCredits appear only for plans where ReelMoney shows credits.\n\nScope: `brand:read`.\n","x-required-scope":"brand:read","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The account, plan and key.","headers":{"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Account"}}}]},"example":{"success":true,"data":{"user":{"id":41,"email":"ja****@example.com"},"plan":{"name":"basic","capabilities":{"plan_name":"basic","metered":"slideshows","slideshow_limit":40,"slideshows_used":12,"slideshows_remaining":28,"automation_limit":2}},"brands":[{"id":7,"name":"Northwind Coffee"}],"api_key":{"name":"Agent key","scopes":["brand:read","media:read","content:write","posts:write"],"last_used_at":"2026-08-18T09:14:22Z"}},"meta":{}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/capabilities":{"get":{"tags":["Discovery"],"operationId":"getCapabilities","summary":"Read the enums and limits the API accepts","description":"The machine readable contract to read before building a generation\nrequest: the design styles and image worlds a slideshow can use, the\noverlay fonts, the aspect ratios, the slide count bounds, the short types,\nthe image sources and whether AI images are affordable right now, the\nplatforms with their caption and title caps, and how often to poll a job.\n\nValues come from the same code that enforces them, so they cannot drift\nfrom reality. Read `version` to notice a change.\n\nScope: any valid key.\n","x-required-scope":null,"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The capability catalog.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Capabilities"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/errors/{code}":{"get":{"tags":["Discovery"],"operationId":"explainError","summary":"Look up an error code","description":"Returns the cause, the fix and whether a retry can help for any code the\nAPI publishes. Call this when a response carries a code the agent does not\nrecognise, instead of guessing from the message.\n\nScope: any valid key.\n","x-required-scope":null,"security":[{"bearerAuth":[]}],"parameters":[{"name":"code","in":"path","required":true,"description":"The error code from a failed response, for example quota_exceeded.","schema":{"$ref":"#/components/schemas/ErrorCode"}}],"responses":{"200":{"description":"The catalog entry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ErrorCatalogEntry"}}}]},"example":{"success":true,"data":{"code":"quota_exceeded","message":"The plan quota for this action is used up.","cause":"The account reached its limit for the current billing period or the free tier limit.","fix":"Wait for the quota to reset, or upgrade the plan. Call GET /api/v1/account to see what is left.","retryable":false,"http_status":403},"meta":{}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"The code is not in the catalog. The response carries every known code\nin `details.known_codes`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"unknown_error_code","message":"That error code is not in the catalog.","retryable":false,"details":{"code":"not_a_code","known_codes":["missing_token","invalid_token","quota_exceeded"]}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/brands":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Discovery"],"operationId":"listBrands","summary":"List the brands the key owner belongs to","description":"A lightweight list with counts. Generation and posting always happen on\nthe brand the key belongs to, so this is mostly for showing a person which\nworkspace a key points at.\n\nScope: `brand:read`.\n","x-required-scope":"brand:read","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The brands.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BrandSummary"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/brand":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Discovery"],"operationId":"getBrand","summary":"Read the brand this key belongs to","description":"The detail view to read before generating: the profile, the brand\nslideshow defaults that shape every generation, and which platforms are\nconnected.\n\nScope: `brand:read`.\n","x-required-scope":"brand:read","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The brand.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/BrandDetail"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/direct_uploads":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"post":{"tags":["Media"],"operationId":"createDirectUpload","summary":"Ask for a presigned upload target","description":"Step one of two for getting a file into ReelMoney. Declare the file, PUT\nthe bytes to the returned URL with the returned headers, then call\n`POST /api/v1/media` with the `signed_id` to keep the upload.\n\nImages may be up to 10 MB and videos up to 200 MB. Allowed types are jpeg,\npng, webp, gif, heic, heif, mp4, webm and quicktime.\n\nScope: `media:write`.\n","x-required-scope":"media:write","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DirectUploadRequest"},"example":{"filename":"kitchen-b-roll.mp4","byte_size":18446231,"checksum":"3l6XuJdvFmn5Xz7SsWlXPA==","content_type":"video/mp4"}}}},"responses":{"201":{"description":"The upload target.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DirectUploadTarget"}}}]},"example":{"success":true,"data":{"signed_id":"eyJfcmFpbHMiOnsiZGF0YSI6MTIzfX0","direct_upload":{"url":"https://storage.example.com/uploads/abc123","headers":{"Content-Type":"video/mp4","Content-Disposition":"attachment; filename=\"kitchen-b-roll.mp4\""}}},"meta":{}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"415":{"description":"The content type is not one the API accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"unsupported_media_type","message":"Invalid content type. Allowed types: image/jpeg, image/png, video/mp4","retryable":false}]}}}},"422":{"description":"The file is larger than the limit for its type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"media_too_large","message":"File too large. Maximum size: 200MB","retryable":false}]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/media":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Media"],"operationId":"listMedia","summary":"Browse media","description":"One list over three sources. `own` is the media this brand owns, `packs`\nis the media inside the packs the owner can reach, and `stock` is the\nshared global library. Stock searches run on image meaning, the other\nsources search on text.\n\nScope: `media:read`.\n","x-required-scope":"media:read","security":[{"bearerAuth":[]}],"parameters":[{"name":"source","in":"query","description":"Which library to read.","schema":{"type":"string","enum":["own","packs","stock"],"default":"own"}},{"name":"query","in":"query","description":"Search text. On stock this runs a meaning based image search.","schema":{"type":"string"}},{"name":"media_type","in":"query","description":"Filter by media type, for example user_upload, product_demo or avatar_video.","schema":{"type":"string"}},{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/PerPage"}],"responses":{"200":{"description":"The media page.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MediaItem"}},"meta":{"allOf":[{"$ref":"#/components/schemas/PaginationMeta"},{"type":"object","properties":{"source":{"type":"string","enum":["own","packs","stock"]},"search_method":{"type":"string","description":"How the list was ordered or matched, for example recent, text or semantic."}}}]}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"The source is not one of own, packs or stock.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Media"],"operationId":"createMedia","summary":"Register an upload as media","description":"Step two of the upload flow. A blob with no owning record is eventually\npurged, so this call is what keeps an upload alive and makes it usable in\na generation request.\n\nScope: `media:write`.\n","x-required-scope":"media:write","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMediaRequest"},"example":{"file":"eyJfcmFpbHMiOnsiZGF0YSI6MTIzfX0","title":"Kitchen b roll","media_type":"product_demo","tags":["kitchen","demo"]}}}},"responses":{"201":{"description":"The media record.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/MediaItem"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/media_packs":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Media"],"operationId":"listMediaPacks","summary":"List the media packs this account can use","description":"The global packs plus the packs the key owner owns. Pass a pack id as\n`image_source.media_pack_id` when creating a slideshow to draw every slide\nbackground from that pack.\n\nScope: `media:read`.\n","x-required-scope":"media:read","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The packs.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MediaPack"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/background_music":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Media"],"operationId":"listBackgroundMusic","summary":"List background music tracks","description":"The tracks a video generation can name through `background_music_id`.\nCovers the shared library, the key owner's own uploads and the global\nstock music.\n\nScope: `media:read`.\n","x-required-scope":"media:read","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The tracks.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BackgroundMusicTrack"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/slideshows":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"post":{"tags":["Generation"],"operationId":"createSlideshow","summary":"Generate an AI slideshow","description":"Writes the slides, art directs them and sources an image for each one.\nAnswers 202 with a job envelope. Poll `GET /api/v1/jobs/{job_id}` every\n`poll_after_seconds` until the status reads succeeded or failed, then read\nthe short for the slides.\n\nLeave `design_style` out and ReelMoney picks a style that the brand has not\nused recently, which keeps a feed from looking repetitive. Set it only when\na caller asked for a specific look.\n\n`image_source.type` of `ai` spends credits. Read\n`GET /api/v1/capabilities` for the estimate and whether the balance covers\nit, or the call fails with `insufficient_credits`.\n\nCounts against the generation ceiling of 20 requests per hour and against\nthe plan slideshow quota.\n\nScope: `content:write`.\n","x-required-scope":"content:write","x-rate-limit":"20 requests per hour, shared with the other generation operations.","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSlideshowRequest"},"examples":{"stockImages":{"summary":"Five slides from the stock library","value":{"context":"Five reasons a pour over tastes better than a drip machine, written for people who are new to specialty coffee.","slide_count":5,"aspect_ratio":"9:16","image_source":{"type":"stock"},"title":"Pour over basics","caption":"Save this before your next brew.","tags":"coffee, pourover, brewing"}},"packWithStyle":{"summary":"Images from a pack in a chosen style","value":{"context":"Three quiet habits that make a morning feel unhurried.","slide_count":3,"aspect_ratio":"4:5","design_style":"editorial_serif","image_source":{"type":"pack","media_pack_id":12}}}}}}},"responses":{"202":{"description":"The slideshow was created and generation was queued. The job id is the\nshort id.\n","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Job"}}}]},"example":{"success":true,"data":{"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"},"meta":{}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/QuotaOrScope"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/slideshows/{id}":{"parameters":[{"$ref":"#/components/parameters/BrandId"},{"name":"id","in":"path","required":true,"description":"The slideshow short id, which is also the job id from the create call.","schema":{"type":"integer"}}],"patch":{"tags":["Generation"],"operationId":"updateSlideshow","summary":"Edit a generated slideshow","description":"Change the title, caption, tags, aspect ratio, gradient settings or the\nslides themselves. Submitted text overlays are normalised server side the\nsame way generated ones are, so fonts, sizes, positions and contrast stay\ninside what the renderer supports.\n\nOnly a settled slideshow may be edited. While a generation or a render is\nrunning the call answers 409 `job_in_progress`.\n\nScope: `content:write`.\n","x-required-scope":"content:write","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSlideshowRequest"},"example":{"title":"Pour over basics","caption":"Save this before your next brew.","slides":[{"id":1,"image_url":"https://cdn.example.com/slides/1.jpg","textOverlays":[{"text":"START WITH FRESH BEANS","fontFamily":"Anton","fontSize":96,"color":"#FFFFFF","strokeColor":"#000000","position":{"x":50,"y":40}}]}]}}}},"responses":{"200":{"description":"The updated slideshow.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Slideshow"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/JobInProgress"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"}}},"put":{"tags":["Generation"],"operationId":"replaceSlideshow","summary":"Edit a generated slideshow (alias of PATCH)","description":"Same behaviour as `PATCH /api/v1/slideshows/{id}`. Fields left out are\nleft alone, so prefer PATCH for clarity. This alias exists for clients that\nonly send PUT.\n\nScope: `content:write`.\n","x-required-scope":"content:write","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSlideshowRequest"}}}},"responses":{"200":{"description":"The updated slideshow.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Slideshow"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/JobInProgress"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/slideshows/{id}/render":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"post":{"tags":["Generation"],"operationId":"renderSlideshow","summary":"Render a slideshow to video","description":"Turns a generated slideshow into an MP4. Use this instead of creating a\nsecond slideshow when a caller wants a video of one they already have.\n\nAnswers 202 with a job envelope whose kind is `render`. The render is done\nwhen the job reports succeeded and `result.video_url` is filled in.\n\nA slideshow that already has a video, or one with a render already in\nflight, answers 409. Counts against the generation ceiling.\n\nScope: `content:write`.\n","x-required-scope":"content:write","x-rate-limit":"20 requests per hour, shared with the other generation operations.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The slideshow short id.","schema":{"type":"integer"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"format":{"type":"string","enum":["video"],"default":"video","description":"Only video is supported today. Photo set rendering is not exposed in v1."}}},"example":{"format":"video"}}}},"responses":{"202":{"description":"The render was queued.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Job"}}}]},"example":{"success":true,"data":{"job_id":8842,"kind":"render","status":"queued","status_message":"Waiting for the job to start.","poll_after_seconds":30,"estimated_completion_at":"2026-08-18T09:21:40Z"},"meta":{}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The slideshow cannot be rendered right now: content is still being\ngenerated, a render is already in flight, or a video already exists.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"job_in_progress","message":"A render for this slideshow is already in flight.","retryable":true,"details":{"render_requested_at":"2026-08-18T09:16:40Z"}}]}}}},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/videos":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"post":{"tags":["Generation"],"operationId":"createVideo","summary":"Generate a hook and demo video","description":"Builds a short video where an avatar clip speaks the hook, followed by the\nproduct demo clips. Answers 202 with a job envelope. Videos take longer\nthan slideshows, so the poll interval comes back as 30 seconds.\n\nName the avatar with `avatar_media_id` (browse\n`GET /api/v1/media?source=stock&media_type=avatar_video`) or with\n`avatar_url`. An avatar is required. Demo clips come from\n`product_demo_media_ids` or `product_demo_urls`, and media ids must belong\nto this brand, to the brand owner or to the global catalog.\n\nCounts against the generation ceiling of 20 requests per hour.\n\nScope: `content:write`.\n","x-required-scope":"content:write","x-rate-limit":"20 requests per hour, shared with the other generation operations.","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVideoRequest"},"example":{"hook_text":"I stopped buying coffee out and this is what changed.","avatar_media_id":3391,"product_demo_media_ids":[8801,8802],"background_music_id":14,"loop_count":1,"title":"Home brew switch","caption":"Three weeks in and I am not going back.","text_settings":{"position":{"x":50,"y":22},"align":"center"}}}}},"responses":{"202":{"description":"The video was created and generation was queued.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Job"}}}]},"example":{"success":true,"data":{"job_id":8850,"kind":"video_generation","status":"processing","status_message":"Generating the video.","poll_after_seconds":30,"estimated_completion_at":"2026-08-18T09:26:11Z"},"meta":{}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/QuotaOrScope"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"422":{"description":"The request is not usable. The most common cause is a missing avatar,\nor a media id this brand cannot reach.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"validation_failed","message":"An avatar is required. Send avatar_media_id or avatar_url.","retryable":false,"details":{"field":"avatar_media_id"}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/jobs/{id}":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Jobs"],"operationId":"getJob","summary":"Poll a generation or render job","description":"One poller for every kind of long running work. The job id is the short\nid returned by the create call.\n\nWait `poll_after_seconds` between polls, which the response repeats in the\n`Retry-After` header. Stop as soon as status reads `succeeded` or\n`failed`. A failed job carries an error with a code from the catalog and\nwhether a retry can help.\n\nScope: `brand:read`.\n","x-required-scope":"brand:read","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The job id, which is the short id.","schema":{"type":"integer"}}],"responses":{"200":{"description":"The job state.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Job"}}}]},"examples":{"processing":{"summary":"Still working","value":{"success":true,"data":{"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"},"meta":{}}},"succeeded":{"summary":"Finished","value":{"success":true,"data":{"job_id":8842,"kind":"slideshow_generation","status":"succeeded","status_message":"The slideshow content is ready.","poll_after_seconds":15,"result":{"short_id":8842,"preview_url":"https://cdn.example.com/slides/8842-1.jpg","video_url":null,"slides":{"count":5}}},"meta":{}}},"failed":{"summary":"Failed","value":{"success":true,"data":{"job_id":8850,"kind":"video_generation","status":"failed","status_message":"The job failed. See error for what went wrong.","poll_after_seconds":30,"error":{"code":"generation_failed","message":"The avatar clip could not be downloaded.","retryable":true}},"meta":{}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No job with that id belongs to this brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"job_not_found","message":"That job does not exist for this brand.","retryable":false}]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/shorts":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Content"],"operationId":"listShorts","summary":"List the brand's shorts","description":"Slideshows and hook and demo videos this brand owns, newest first.\nFilter by type, status and creation window.\n\nScope: `brand:read`.\n","x-required-scope":"brand:read","security":[{"bearerAuth":[]}],"parameters":[{"name":"short_type","in":"query","description":"Filter by type. Send all to drop the filter.","schema":{"type":"string","enum":["SLIDESHOW","UGC","all"]}},{"name":"status","in":"query","description":"Filter by raw short status, for example draft, processing, success_slideshow or failed.","schema":{"type":"string"}},{"name":"created_after","in":"query","description":"Only shorts created at or after this time.","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","description":"Only shorts created at or before this time.","schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/PerPage"}],"responses":{"200":{"description":"The shorts page.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Short"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationFailed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/shorts/{id}":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Content"],"operationId":"getShort","summary":"Read one short in full","description":"Everything the list leaves out: the type data with the slides, the\nthumbnail and every post made from this short.\n\nScope: `brand:read`.\n","x-required-scope":"brand:read","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"The short.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ShortDetail"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/connections":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Connections"],"operationId":"listConnections","summary":"List connected social accounts","description":"What the brand can post to right now. Each account reports its token\nstatus, its platform posting headroom and a single `ready_to_post` flag to\nbranch on. Platforms with no account are listed in `not_connected`\nalongside the steps a person follows to add one.\n\nAccess tokens never leave the server, so no token field appears here.\n\nScope: `brand:read`.\n","x-required-scope":"brand:read","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The connections.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ConnectionsPayload"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/connections/link":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"post":{"tags":["Connections"],"operationId":"createConnectionLink","summary":"Build a link for connecting an account","description":"Connecting a social account always needs a person, so the API hands back\nthe authorize URL and the steps instead of pretending it can do it alone.\nGive the link to the account owner, let them approve access in a browser,\nthen call `GET /api/v1/connections` again to see the account appear.\n\nAuthorize links go stale after a few minutes, so build one when the person\nis ready to click it.\n\nScope: `brand:read`.\n","x-required-scope":"brand:read","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["platform"],"properties":{"platform":{"type":"string","enum":["tiktok","youtube","instagram","linkedin"]}}},"example":{"platform":"tiktok"}}}},"responses":{"200":{"description":"The link and the steps.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ConnectLink"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"The platform is not one ReelMoney posts to.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"unsupported_platform","message":"That platform is not one ReelMoney posts to.","retryable":false,"details":{"platform":"threads","supported_platforms":["tiktok","youtube","instagram","linkedin"]}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"This ReelMoney server has no client id or callback URL for that platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/v1/posts/validate":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"post":{"tags":["Posting"],"operationId":"validatePost","summary":"Check a post before publishing it","description":"A dry run that touches nothing and enqueues nothing. Takes the same body\nas `POST /api/v1/posts` and answers with one verdict per account: is the\nshort ready to publish, is the connection healthy, is the caption or title\ninside the platform cap, is there posting headroom left, and is the\nscheduled time far enough ahead.\n\nCall this before every post. It costs nothing and it turns a partial\nfailure into a fix.\n\nScope: `posts:read`.\n","x-required-scope":"posts:read","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostRequest"},"example":{"short_id":8842,"platforms":["tiktok"],"scheduled_at":"2026-08-19T15:00:00Z"}}}},"responses":{"200":{"description":"The verdicts. A verdict list can report problems while the call itself succeeds.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PostValidation"}}}]},"example":{"success":true,"data":{"ok":false,"short_id":8842,"scheduled_at":"2026-08-19T15:00:00Z","issues":[],"verdicts":[{"social_data_id":55,"platform":"tiktok","account_name":"northwindcoffee","token_status":"expired","rate_limit":{"used":3,"max":15,"remaining":12,"limited":false,"resets_at":"2026-08-19T00:00:00Z","label":"15 posts per 24 hours"},"ok":false,"issues":[{"code":"token_expired","message":"The tiktok connection reports expired and needs a fresh connect link.","retryable":false}]}],"platform_rules":{"tiktok":{"title_max":90,"caption_max":4000,"posting_modes":["draft","direct"]}}},"meta":{}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No short with that id belongs to this brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/posts":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Posting"],"operationId":"listPosts","summary":"List posts and scheduled posts","description":"Every post made from this brand's shorts, newest first, with status,\nscheduled time, published time and the link on the platform once it lands.\n\nScope: `posts:read`.\n","x-required-scope":"posts:read","security":[{"bearerAuth":[]}],"parameters":[{"name":"short_id","in":"query","description":"Only posts made from this short.","schema":{"type":"integer"}},{"name":"status","in":"query","schema":{"type":"string","enum":["draft","pending","success","failed","scheduled","cancelled","retrying"]}},{"name":"platform","in":"query","schema":{"type":"string","enum":["tiktok","youtube","instagram","linkedin"]}},{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/PerPage"}],"responses":{"200":{"description":"The posts page.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Post"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Posting"],"operationId":"createPost","summary":"Publish or schedule a post","description":"Sends a short to one or more connected accounts. Omit `scheduled_at` to\npublish now, or send an ISO 8601 time at least 5 minutes ahead to schedule.\n\nPosting is partial by design. One account with a stale token never stops\nthe others, so read `overall_status` and then walk `results`: each entry\ncarries its own status and, when it failed, its own error code. A run where\nevery account failed answers 422 and keeps the same per account detail in\n`details.results`.\n\nPublishing changes the world, so a human facing agent should confirm with\nthe person first. Counts against the posting ceiling of 30 requests per\nhour.\n\nScope: `posts:write`.\n","x-required-scope":"posts:write","x-rate-limit":"30 requests per hour.","security":[{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostRequest"},"examples":{"publishNow":{"summary":"Publish to two accounts now","value":{"short_id":8842,"social_data_ids":[55,56]}},"scheduleWithCustomCaptions":{"summary":"Schedule with a caption per account","value":{"short_id":8842,"platforms":["tiktok","instagram"],"scheduled_at":"2026-08-19T15:00:00Z","posting_mode":"direct","auto_add_music":true,"platform_customizations":{"55":{"title":"Pour over basics","caption":"Save this before your next brew."},"56":{"caption":"Three weeks in and I am not going back."}}}}}}}},"responses":{"202":{"description":"The post was accepted for one or more accounts. Read `overall_status`\nfor the verdict: success, partial or failed for the accounts that were\nreachable.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PostBatchResult"}}}]},"example":{"success":true,"data":{"short_id":8842,"overall_status":"partial","results":[{"social_data_id":55,"platform":"tiktok","account_name":"northwindcoffee","share_id":9901,"status":"pending","scheduled_at":null},{"social_data_id":null,"platform":"instagram","account_name":null,"share_id":null,"status":"failed","error":{"code":"platform_not_connected","message":"No instagram account is connected. Call POST /api/v1/connections/link to add one.","retryable":false}}]},"meta":{}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"422":{"description":"Nothing could be posted. The code names the first reason and\n`details.results` keeps the per account detail.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"short_not_postable","message":"The short has no video and no slides, so there is nothing to publish.","retryable":true}]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/posts/{id}":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"delete":{"tags":["Posting"],"operationId":"cancelPost","summary":"Cancel a scheduled post","description":"Pulls back a post that has not gone out yet. Only posts sitting in draft,\npending or scheduled can be cancelled; anything already published or in\nflight answers 409 `not_cancellable`.\n\nScope: `posts:write`.\n","x-required-scope":"posts:write","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The post id from GET /api/v1/posts.","schema":{"type":"integer"}}],"responses":{"200":{"description":"The cancelled post.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Post"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The post can no longer be cancelled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"not_cancellable","message":"That post can no longer be cancelled.","retryable":false,"details":{"post_id":9901,"status":"success","cancellable_statuses":["draft","pending","scheduled"]}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/analytics":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Analytics"],"operationId":"listAnalytics","summary":"Read account level analytics","description":"A summary per connected account with an analytics adapter. Read\n`supported_platforms` first: accounts on other platforms are simply not\nlisted here.\n\nScope: `analytics:read`.\n","x-required-scope":"analytics:read","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The account summaries.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"accounts":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsAccount"}},"supported_platforms":{"type":"array","items":{"type":"string"}},"note":{"type":"string"}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/analytics/accounts/{social_data_id}":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"get":{"tags":["Analytics"],"operationId":"getAccountAnalytics","summary":"Read post metrics for one account","description":"Post by post numbers for one connected account, plus the account summary.\nFilter to only the posts ReelMoney published with `filter=reelmoney`, and\nsort by any of the count columns.\n\nScope: `analytics:read`.\n","x-required-scope":"analytics:read","security":[{"bearerAuth":[]}],"parameters":[{"name":"social_data_id","in":"path","required":true,"description":"The connected account id from GET /api/v1/connections.","schema":{"type":"integer"}},{"name":"period","in":"query","schema":{"type":"string","enum":["24h","7d","30d","90d"],"default":"30d"}},{"name":"filter","in":"query","description":"Send reelmoney to see only the posts published through ReelMoney.","schema":{"type":"string","enum":["all","reelmoney"],"default":"all"}},{"name":"sort","in":"query","schema":{"type":"string","enum":["posted_at","view_count","like_count","comment_count","share_count","favorite_count"],"default":"posted_at"}},{"name":"dir","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/PerPage"}],"responses":{"200":{"description":"The metrics page.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AccountAnalytics"},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/analytics/accounts/{social_data_id}/refresh":{"parameters":[{"$ref":"#/components/parameters/BrandId"}],"post":{"tags":["Analytics"],"operationId":"refreshAccountAnalytics","summary":"Pull fresh numbers from the platform","description":"Starts a refresh against the platform and answers 202. Read the account\nagain after `poll_after_seconds` for the new numbers.\n\nAn account refreshed recently is inside a cooldown and answers 429 with\nthe seconds left in `details.retry_after_seconds`. That is a platform\nfriendliness limit, not the API key ceiling.\n\nScope: `analytics:read`.\n","x-required-scope":"analytics:read","security":[{"bearerAuth":[]}],"parameters":[{"name":"social_data_id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"202":{"description":"The refresh was queued.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessEnvelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"social_data_id":{"type":"integer"},"status":{"type":"string","const":"refreshing"},"poll_after_seconds":{"type":"integer"},"message":{"type":"string"}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"description":"Either the account is inside its refresh cooldown, or the API key\npassed a request ceiling. Both carry `rate_limited` and a\n`Retry-After` header; `details.bucket` tells them apart.\n","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"rate_limited","message":"This account was refreshed recently. Analytics can be refreshed once every 15 minutes.","retryable":true,"details":{"retry_after_seconds":420,"bucket":"analytics_refresh"}}]}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"rm_key","description":"An API key created in ReelMoney under Settings and API keys. Keys start\nwith `rm_` and are shown once at creation. Send it as\n`Authorization: Bearer rm_your_key_here`. A missing or unusable key\nanswers 401 with a `WWW-Authenticate: Bearer realm=\"ReelMoney\",\nerror=\"invalid_token\"` header.\n"}},"parameters":{"BrandId":{"name":"brand_id","in":"query","required":false,"description":"Brand to act on, as an id from `GET /api/v1/brands` or\n`GET /api/v1/account`. Omit it and the request acts on the key's own\nbrand, exactly as before this parameter existed. Naming a different\nbrand needs the `brands:all` scope on the key and the brand must be\nowned by the same account: without the scope the API answers 403\n`missing_scope` with `details.required_scope` of `brands:all`, and an\nid the account does not own answers 404 `not_found`. The `X-Brand-Id`\nheader carries the same value; the query parameter wins when both are\nsent.\n","schema":{"type":"string"}},"Page":{"name":"page","in":"query","description":"Page number, starting at 1.","schema":{"type":"integer","minimum":1,"default":1}},"PerPage":{"name":"per_page","in":"query","description":"Records per page. Values above 50 are clamped to 50.","schema":{"type":"integer","minimum":1,"maximum":50,"default":25}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"A unique string of your own, at most 255 characters. Retrying with the\nsame key and the same body replays the first response and sets\n`meta.idempotent_replay` to true. The same key with a different body\nanswers 409 `idempotency_conflict`. Keys are remembered for 24 hours.\n","schema":{"type":"string","maxLength":255}}},"headers":{"RetryAfter":{"description":"Seconds to wait before the next call. Matches poll_after_seconds on job responses.","schema":{"type":"integer"}},"XRateLimitLimit":{"description":"Requests allowed in the current window for this key.","schema":{"type":"integer"}},"XRateLimitRemaining":{"description":"Requests left in the current window for this key.","schema":{"type":"integer"}},"XRateLimitWindow":{"description":"Length of the current window in seconds.","schema":{"type":"integer"}},"WWWAuthenticate":{"description":"The bearer challenge, sent with every 401.","schema":{"type":"string","examples":["Bearer realm=\"ReelMoney\", error=\"invalid_token\""]}}},"responses":{"Unauthorized":{"description":"No key, or a key that is unusable. Codes: `missing_token`,\n`invalid_token`, `key_revoked`, `key_expired`.\n","headers":{"WWW-Authenticate":{"$ref":"#/components/headers/WWWAuthenticate"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"invalid_token","message":"The API key is not valid.","retryable":false}]}}}},"Forbidden":{"description":"The key does not carry the scope this operation needs. The response names\nthe scope in `details.required_scope`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"missing_scope","message":"The API key does not have the permission this endpoint requires.","retryable":false,"details":{"required_scope":"content:write","key_scopes":["brand:read"]}}]}}}},"QuotaOrScope":{"description":"Either the key lacks the scope (`missing_scope`) or the plan quota for\nthis action is used up (`quota_exceeded`). Read `details` for the numbers\nand call `GET /api/v1/account` for what is left.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"quota_exceeded","message":"You have used all 40 slideshow generations included in your plan this month.","retryable":false,"details":{"slideshow_limit_exceeded":true,"limit":40,"used":40,"remaining":0}}]}}}},"InsufficientCredits":{"description":"The account does not hold enough credits for this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"insufficient_credits","message":"Insufficient credits. You need 5 credits to generate AI images.","retryable":false,"details":{"credits_required":5,"current_credits":1}}]}}}},"NotFound":{"description":"The record does not exist, or it belongs to another brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"not_found","message":"That record does not exist, or this key cannot see it.","retryable":false}]}}}},"BadRequest":{"description":"A required parameter was missing, or the body could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"bad_request","message":"The request could not be read.","retryable":false,"details":{"parameter":"platform"}}]}}}},"ValidationFailed":{"description":"A field is missing or holds a value the operation does not accept.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"validation_failed","message":"slide_count must be 0 (auto) or between 3 and 12.","retryable":false,"details":{"field":"slide_count"}}]}}}},"Conflict":{"description":"The record is in a state that does not allow this action.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"JobInProgress":{"description":"Work is running on this record. Poll `GET /api/v1/jobs/{job_id}` until it\nsettles, then send the request again.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"job_in_progress","message":"A job is still running on this record.","retryable":true,"details":{"status":"processing"}}]}}}},"IdempotencyConflict":{"description":"This `Idempotency-Key` was already used with a different body, or the first\nrequest carrying it is still running.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"idempotency_conflict","message":"This Idempotency-Key was already used with a different request body.","retryable":false,"details":{"idempotency_key_reused_with_different_body":true,"endpoint":"POST /api/v1/slideshows"}}]}}}},"RateLimited":{"description":"A request ceiling was passed: 300 per 5 minutes across the API, 20 per\nhour for generation, 30 per hour for posting. Wait\n`details.retry_after_seconds` and try again.\n","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Window":{"$ref":"#/components/headers/XRateLimitWindow"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"errors":[{"code":"rate_limited","message":"This API key sent too many requests.","retryable":true,"details":{"retry_after_seconds":300,"limit":300,"window_seconds":300,"bucket":"global"}}]}}}}},"schemas":{"SuccessEnvelope":{"type":"object","title":"Success envelope","description":"The shape of every successful response.","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"description":"The payload. Its shape depends on the operation."},"meta":{"type":"object","description":"Extra information about the response, for example `pagination` on a\nlist or `idempotent_replay` on a replayed create.\n","additionalProperties":true}}},"ErrorResponse":{"type":"object","title":"Error envelope","description":"The shape of every failed response.","required":["success","errors"],"properties":{"success":{"type":"boolean","const":false},"errors":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/ApiError"}},"meta":{"type":"object","additionalProperties":true}}},"ApiError":{"type":"object","title":"Error","required":["code","message","retryable"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"A plain sentence for a person. Branch on code, not on this."},"retryable":{"type":"boolean","description":"True when sending the same request again later can succeed."},"details":{"type":"object","description":"Machine readable specifics, for example the offending field or the quota numbers.","additionalProperties":true}}},"ErrorCode":{"type":"string","title":"Error code","description":"Every code the API publishes. Look one up with\n`GET /api/v1/errors/{code}` for its cause and its fix.\n","enum":["missing_token","invalid_token","key_revoked","key_expired","missing_scope","approval_required","quota_exceeded","insufficient_credits","validation_failed","bad_request","not_found","unknown_error_code","job_not_found","conflict","job_in_progress","generation_failed","idempotency_conflict","media_too_large","unsupported_media_type","platform_not_connected","token_expired","platform_rate_limited","unsupported_platform","platform_not_configured","short_not_postable","caption_too_long","title_too_long","schedule_too_soon","not_cancellable","rate_limited","internal_error"]},"ErrorCatalogEntry":{"type":"object","title":"Error catalog entry","required":["code","message","cause","fix","retryable","http_status"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"cause":{"type":"string","description":"Why this happens."},"fix":{"type":"string","description":"What to do about it."},"retryable":{"type":"boolean"},"http_status":{"type":"integer"}}},"Pagination":{"type":"object","title":"Pagination","properties":{"current_page":{"type":"integer"},"per_page":{"type":"integer"},"total_pages":{"type":"integer"},"total_count":{"type":"integer"},"next_page":{"type":["integer","null"]},"prev_page":{"type":["integer","null"]}}},"PaginationMeta":{"type":"object","properties":{"pagination":{"$ref":"#/components/schemas/Pagination"}}},"Account":{"type":"object","title":"Account","properties":{"user":{"type":"object","properties":{"id":{"type":"integer"},"email":{"type":"string","description":"Masked, enough to recognise the account and no more."}}},"plan":{"type":"object","properties":{"name":{"type":"string"},"capabilities":{"type":"object","description":"What the plan allows, including the slideshow quota used and\nremaining and the automation allowance.\n","additionalProperties":true}}},"credits":{"type":"integer","description":"Only present on plans where ReelMoney shows a credit balance."},"brands":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"}}}},"api_key":{"type":"object","properties":{"name":{"type":"string"},"scopes":{"type":"array","description":"The resource scopes this key grants, with legacy scopes already expanded.","items":{"type":"string","enum":["brand:read","media:read","media:write","content:write","posts:read","posts:write","analytics:read"]}},"last_used_at":{"type":["string","null"],"format":"date-time"}}}}},"BrandSummary":{"type":"object","title":"Brand","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":["string","null"]},"website":{"type":["string","null"]},"language":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"stats":{"type":"object","properties":{"total_shorts":{"type":"integer"},"total_product_demos":{"type":"integer"},"total_hooks":{"type":"integer"},"connected_social_accounts":{"type":"integer"}}}}},"BrandDetail":{"allOf":[{"$ref":"#/components/schemas/BrandSummary"},{"type":"object","properties":{"categories":{"type":"array","items":{"type":"string"}},"slideshow_settings":{"type":"object","description":"The brand defaults that shape every slideshow generation.","properties":{"default_context":{"type":["string","null"]},"default_font_family":{"type":["string","null"]},"slide_overrides":{"type":["object","null"],"additionalProperties":true},"append_slots":{"type":["array","null"],"items":{"type":"object","additionalProperties":true}}}},"connections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"platform":{"type":"string"},"account_name":{"type":["string","null"]},"status":{"type":"string"},"connected":{"type":"boolean"}}}}}}]},"Capabilities":{"type":"object","title":"Capabilities","description":"Enums and limits, derived from the code that enforces them. Fields are\nadded over time and never renamed without a version bump.\n","properties":{"version":{"type":"string","description":"Bumped when a field changes meaning or disappears."},"design_styles":{"type":"array","description":"The slideshow design recipes. Pass a `key` as `design_style`, or leave it out to let ReelMoney vary the look.","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"heading_font":{"type":"string"},"body_font":{"type":"string"},"text_case":{"type":"string"},"image_worlds":{"type":"array","items":{"type":"string"}}}}},"image_worlds":{"type":"array","description":"The image aesthetics a generation can land in.","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"}}}},"content_formats":{"type":"array","items":{"type":"string"}},"fonts":{"type":"object","properties":{"families":{"type":"array","items":{"type":"string"}},"weights":{"type":"array","items":{"type":"integer"}},"min_font_size":{"type":"integer"},"max_font_size":{"type":"integer"},"note":{"type":"string"}}},"aspect_ratios":{"type":"array","items":{"type":"object","properties":{"ratio":{"type":"string"},"width":{"type":"integer"},"height":{"type":"integer"},"default":{"type":"boolean"}}}},"slide_count":{"type":"object","properties":{"auto_value":{"type":"integer","description":"Send this value to let the model choose the number of slides."},"min":{"type":"integer"},"max":{"type":"integer"},"default":{"type":"integer"}}},"short_types":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"endpoint":{"type":"string"}}}},"image_sources":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["pack","stock","ai"]},"name":{"type":"string"},"description":{"type":"string"},"requires_credits":{"type":"boolean"},"credits_estimate":{"type":"integer"},"credits_available":{"type":["integer","null"]},"available":{"type":["boolean","null"]}}}},"platforms":{"type":"array","items":{"$ref":"#/components/schemas/PlatformCapability"}},"jobs":{"type":"object","properties":{"endpoint":{"type":"string"},"statuses":{"type":"array","items":{"type":"string"}},"kinds":{"type":"array","items":{"type":"string"}},"poll_after_seconds":{"type":"object","additionalProperties":{"type":"integer"}},"note":{"type":"string"}}}},"additionalProperties":true},"PlatformCapability":{"type":"object","title":"Platform capability","properties":{"platform":{"type":"string","enum":["tiktok","youtube","instagram","linkedin"]},"name":{"type":"string"},"supports":{"type":"object","properties":{"video":{"type":"boolean"},"photos":{"type":"boolean"}}},"title_max_length":{"type":["integer","null"]},"caption_max_length":{"type":["integer","null"]},"posting_modes":{"type":"array","items":{"type":"string"}},"default_posting_mode":{"type":"string"},"photo_post":{"type":["object","null"],"properties":{"min_photos":{"type":"integer"},"max_photos":{"type":"integer"}}},"rate_limit":{"type":["object","null"],"additionalProperties":true},"notes":{"type":"string"}}},"DirectUploadRequest":{"type":"object","title":"Direct upload request","description":"Send the fields flat, or nested under a `blob` key. Both are accepted.\n","required":["filename","byte_size","content_type"],"properties":{"filename":{"type":"string"},"byte_size":{"type":"integer","description":"Size in bytes. Images up to 10 MB, videos up to 200 MB."},"checksum":{"type":"string","description":"Base64 MD5 of the file, used by storage to verify the upload."},"content_type":{"type":"string","enum":["image/jpeg","image/png","image/webp","image/gif","image/heic","image/heif","video/mp4","video/webm","video/quicktime","video/mov"]},"metadata":{"type":"object","additionalProperties":true}}},"DirectUploadTarget":{"type":"object","title":"Direct upload target","properties":{"signed_id":{"type":"string","description":"Send this back as `file` in POST /api/v1/media once the bytes are uploaded."},"direct_upload":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"PUT the raw bytes here."},"headers":{"type":"object","description":"Send every one of these headers with the PUT.","additionalProperties":{"type":"string"}}}}}},"CreateMediaRequest":{"type":"object","title":"Create media request","required":["file"],"properties":{"file":{"type":"string","description":"The signed_id from POST /api/v1/direct_uploads."},"signed_id":{"type":"string","description":"Accepted as an alias of `file`."},"title":{"type":"string","description":"Defaults to the uploaded filename."},"media_type":{"type":"string","enum":["user_upload","product_demo"],"default":"user_upload"},"tags":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"string","description":"A comma separated list."}]}}},"MediaItem":{"type":"object","title":"Media","properties":{"id":{"type":"integer"},"media_type":{"type":"string"},"title":{"type":"string"},"file_url":{"type":["string","null"],"format":"uri"},"thumbnail_url":{"type":["string","null"],"format":"uri"},"tags":{"type":"array","items":{"type":"string"}},"source":{"type":"string","enum":["own","packs","stock"]},"created_at":{"type":"string","format":"date-time"}}},"MediaPack":{"type":"object","title":"Media pack","properties":{"id":{"type":"integer"},"name":{"type":"string"},"description":{"type":["string","null"]},"global":{"type":"boolean","description":"True for the shared packs every account can use."},"media_count":{"type":"integer"},"preview_urls":{"type":"array","items":{"type":"string","format":"uri"}}}},"BackgroundMusicTrack":{"type":"object","title":"Background music track","properties":{"id":{"type":"integer"},"title":{"type":"string"},"source":{"type":"string","enum":["library","stock"]},"global":{"type":"boolean"},"duration":{"type":["integer","null"],"description":"Seconds, when storage recorded it."},"file_url":{"type":["string","null"],"format":"uri"},"preview_url":{"type":["string","null"],"format":"uri"}}},"CreateSlideshowRequest":{"type":"object","title":"Create slideshow request","required":["context"],"properties":{"context":{"type":"string","maxLength":3500,"description":"What the slideshow should be about, in plain language. Say who it is\nfor and what it should get across. This is the single biggest lever on\nquality.\n"},"slide_count":{"type":"integer","description":"0 lets the model choose. Otherwise 3 to 12.","default":5},"aspect_ratio":{"type":"string","description":"Read the exact list from GET /api/v1/capabilities.","default":"9:16","examples":["9:16","4:5","1:1"]},"image_source":{"type":"object","properties":{"type":{"type":"string","enum":["pack","stock","ai"],"default":"stock","description":"`stock` searches the shared library, `pack` walks one of your media\npacks, and `ai` generates a fresh image per slide and spends\ncredits.\n"},"media_pack_id":{"type":"integer","description":"Required when type is pack."}}},"design_style":{"type":"string","description":"One of the `design_styles` keys. Leave it out and ReelMoney picks a\nlook the brand has not used recently, which keeps a feed varied.\n"},"title":{"type":"string"},"caption":{"type":"string"},"language":{"type":"string"},"tags":{"type":"string","description":"A comma separated list."}}},"UpdateSlideshowRequest":{"type":"object","title":"Update slideshow request","description":"Only the fields you send are changed.","properties":{"title":{"type":"string"},"caption":{"type":"string"},"tags":{"type":"string"},"aspect_ratio":{"type":"string"},"gradient_enabled":{"type":"boolean"},"gradient_opacity":{"type":"number"},"slides":{"type":"array","maxItems":30,"items":{"$ref":"#/components/schemas/Slide"}}}},"Slide":{"type":"object","title":"Slide","properties":{"id":{"type":["integer","string"]},"image_url":{"type":"string","format":"uri"},"image":{"type":"string"},"duration":{"type":"number"},"image_search_query":{"type":"string"},"textOverlays":{"type":"array","maxItems":10,"description":"Overlays are normalised server side: fonts outside the supported list\nfall back to Inter, sizes are clamped, and low contrast pills are\ncorrected.\n","items":{"$ref":"#/components/schemas/TextOverlay"}}}},"TextOverlay":{"type":"object","title":"Text overlay","properties":{"text":{"type":"string"},"fontFamily":{"type":"string","description":"Read the supported families from GET /api/v1/capabilities."},"fontSize":{"type":"integer"},"fontWeight":{"type":"integer"},"color":{"type":"string","description":"A hex color."},"backgroundColor":{"type":"string","description":"A hex color turns the overlay into a pill. Use transparent for plain text."},"strokeColor":{"type":"string","description":"A hex color, or the string none for no stroke."},"strokeWidth":{"type":"number","description":"Leave it out with a hex strokeColor for a stroke that scales with the font size."},"position":{"type":"object","properties":{"x":{"type":"number"},"y":{"type":"number"}}},"align":{"type":"string"}},"additionalProperties":true},"CreateVideoRequest":{"type":"object","title":"Create video request","description":"A hook and demo video. The avatar is required.","properties":{"hook_text":{"type":"string","description":"The line the avatar speaks and the text shown on screen."},"avatar_media_id":{"type":"integer","description":"A media id from GET /api/v1/media?source=stock&media_type=avatar_video, or from your own library."},"avatar_url":{"type":"string","format":"uri","description":"Use instead of avatar_media_id when the clip lives outside ReelMoney."},"product_demo_media_ids":{"type":"array","items":{"type":"integer"}},"product_demo_urls":{"type":"array","items":{"type":"string","format":"uri"}},"background_music_id":{"type":"integer","description":"An id from GET /api/v1/background_music."},"loop_count":{"type":"integer","minimum":1,"maximum":5,"default":1},"text_settings":{"type":"object","properties":{"position":{"type":"object","properties":{"x":{"type":"number"},"y":{"type":"number"}}},"align":{"type":"string"},"style_id":{"type":["integer","string"]},"font_size":{"type":"integer"}}},"title":{"type":"string"},"caption":{"type":"string"}},"anyOf":[{"required":["avatar_media_id"]},{"required":["avatar_url"]}]},"Job":{"type":"object","title":"Job","description":"The state of one piece of long running work. The job id is the short id,\nso the same id reads the finished content later.\n","required":["job_id","kind","status","status_message","poll_after_seconds"],"properties":{"job_id":{"type":"integer"},"kind":{"type":"string","enum":["slideshow_generation","video_generation","render"]},"status":{"type":"string","enum":["queued","processing","succeeded","failed"]},"status_message":{"type":"string"},"poll_after_seconds":{"type":"integer","description":"Wait this long before polling again. Also sent as the Retry-After header."},"estimated_completion_at":{"type":"string","format":"date-time","description":"Present while the job is queued or processing. Guidance, not a promise."},"error":{"$ref":"#/components/schemas/ApiError"},"result":{"type":"object","description":"Present once the status reads succeeded.","properties":{"short_id":{"type":"integer"},"preview_url":{"type":["string","null"],"format":"uri"},"video_url":{"type":["string","null"],"format":"uri"},"slides":{"type":"object","properties":{"count":{"type":"integer"}}}}}}},"Short":{"type":"object","title":"Short","properties":{"id":{"type":"integer"},"title":{"type":["string","null"]},"short_type":{"type":"string","enum":["SLIDESHOW","UGC"]},"status":{"type":"string","description":"The raw record status. Use GET /api/v1/jobs/{id} for the job view of it."},"caption":{"type":["string","null"]},"language":{"type":["string","null"]},"video_url":{"type":["string","null"],"format":"uri"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ShortDetail":{"allOf":[{"$ref":"#/components/schemas/Short"},{"type":"object","properties":{"type_data":{"type":"object","description":"The generated content, including the slides for a slideshow.","additionalProperties":true},"thumbnail_url":{"type":["string","null"],"format":"uri"},"shares":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"platform":{"type":"string"},"status":{"type":"string"},"scheduled_at":{"type":["string","null"],"format":"date-time"},"posted_at":{"type":["string","null"],"format":"date-time"},"external_url":{"type":["string","null"]},"error_message":{"type":["string","null"]}}}}}}]},"Slideshow":{"type":"object","title":"Slideshow","properties":{"id":{"type":"integer"},"title":{"type":["string","null"]},"caption":{"type":["string","null"]},"short_type":{"type":"string"},"status":{"type":"string"},"language":{"type":["string","null"]},"type_data":{"type":"object","additionalProperties":true},"video_url":{"type":["string","null"],"format":"uri"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"ConnectionsPayload":{"type":"object","title":"Connections","properties":{"connections":{"type":"array","items":{"$ref":"#/components/schemas/Connection"}},"not_connected":{"type":"array","description":"Platforms with no connected account on this brand.","items":{"type":"string","enum":["tiktok","youtube","instagram","linkedin"]}},"platform_rules":{"type":"object","description":"Caption and title caps and posting modes per platform.","additionalProperties":{"$ref":"#/components/schemas/PlatformRules"}},"connect_steps":{"type":"string","description":"The steps to hand to a person who needs to connect an account."}}},"Connection":{"type":"object","title":"Connection","properties":{"id":{"type":"integer","description":"The social_data_id used when posting."},"platform":{"type":"string"},"account_name":{"type":["string","null"]},"avatar_url":{"type":["string","null"]},"status":{"type":"string","description":"The token state, for example active or expired."},"ready_to_post":{"type":"boolean","description":"True when the token is healthy and the platform still has headroom."},"rate_limit":{"oneOf":[{"$ref":"#/components/schemas/PlatformRateLimit"},{"type":"null"}]},"connected_at":{"type":["string","null"],"format":"date-time"}}},"PlatformRateLimit":{"type":"object","title":"Platform posting headroom","properties":{"used":{"type":"integer"},"max":{"type":"integer"},"remaining":{"type":"integer"},"limited":{"type":"boolean"},"resets_at":{"type":["string","null"],"format":"date-time"},"label":{"type":"string"}}},"PlatformRules":{"type":"object","title":"Platform rules","properties":{"title_max":{"type":["integer","null"]},"caption_max":{"type":["integer","null"]},"photo_count":{"type":"object","properties":{"min":{"type":"integer"},"max":{"type":"integer"}}},"post_formats":{"type":"array","items":{"type":"string"}},"posting_modes":{"type":"array","items":{"type":"string"}},"rate_limit":{"type":"string"},"note":{"type":"string"}}},"ConnectLink":{"type":"object","title":"Connect link","properties":{"platform":{"type":"string"},"connect_url":{"type":"string","format":"uri","description":"The authorize URL a person opens in a browser."},"state":{"type":"string","description":"The brand id, carried through the platform callback."},"instructions":{"type":"string"},"expires_note":{"type":"string"}}},"PostRequest":{"type":"object","title":"Post request","description":"The same body works for the dry run and the real post. Name accounts by\n`social_data_ids`, or name `platforms` to reach every connected account on\nthem.\n","required":["short_id"],"properties":{"short_id":{"type":"integer"},"social_data_ids":{"type":"array","items":{"type":"integer"}},"platforms":{"type":"array","items":{"type":"string","enum":["tiktok","youtube","instagram","linkedin"]}},"scheduled_at":{"type":"string","format":"date-time","description":"Leave it out to publish now. When present it must be at least 5 minutes ahead."},"post_format":{"type":"string","description":"For example video or slideshow. ReelMoney picks a sensible default per short type."},"posting_mode":{"type":"string","enum":["draft","direct"],"description":"TikTok only. Draft drops the post into the TikTok inbox for a person to finish in the app."},"auto_add_music":{"type":"boolean"},"platform_customizations":{"type":"object","description":"Per account title and caption overrides, keyed by social_data_id as a string.","additionalProperties":{"type":"object","properties":{"title":{"type":"string"},"caption":{"type":"string"}}}}}},"PostValidation":{"type":"object","title":"Post validation","properties":{"ok":{"type":"boolean","description":"True only when there are no request issues and every verdict is ok."},"short_id":{"type":["integer","null"]},"scheduled_at":{"type":["string","null"],"format":"date-time"},"issues":{"type":"array","description":"Problems with the request itself rather than with one account.","items":{"$ref":"#/components/schemas/ApiError"}},"verdicts":{"type":"array","items":{"$ref":"#/components/schemas/PostVerdict"}},"platform_rules":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/PlatformRules"}}}},"PostVerdict":{"type":"object","title":"Post verdict","properties":{"social_data_id":{"type":["integer","string","null"]},"platform":{"type":["string","null"]},"account_name":{"type":["string","null"]},"token_status":{"type":"string"},"rate_limit":{"oneOf":[{"$ref":"#/components/schemas/PlatformRateLimit"},{"type":"null"}]},"ok":{"type":"boolean"},"issues":{"type":"array","items":{"$ref":"#/components/schemas/ApiError"}}}},"PostBatchResult":{"type":"object","title":"Post result","description":"One result per account. Posting is partial by design, so read\n`overall_status` and then walk `results`.\n","properties":{"short_id":{"type":"integer"},"overall_status":{"type":"string","enum":["success","partial","failed"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/PostResultItem"}}}},"PostResultItem":{"type":"object","title":"Post result item","properties":{"social_data_id":{"type":["integer","string","null"]},"platform":{"type":["string","null"]},"account_name":{"type":["string","null"]},"share_id":{"type":["integer","null"],"description":"The post id, usable with DELETE /api/v1/posts/{id} while it is still cancellable."},"status":{"type":"string"},"scheduled_at":{"type":["string","null"],"format":"date-time"},"error":{"$ref":"#/components/schemas/ApiError"}}},"Post":{"type":"object","title":"Post","properties":{"id":{"type":"integer"},"short_id":{"type":"integer"},"platform":{"type":"string"},"account_name":{"type":["string","null"]},"social_data_id":{"type":["integer","null"]},"status":{"type":"string","enum":["draft","pending","success","failed","scheduled","cancelled","retrying"]},"scheduled_at":{"type":["string","null"],"format":"date-time"},"posted_at":{"type":["string","null"],"format":"date-time"},"external_url":{"type":["string","null"],"description":"The link on the platform. Present only once the post landed."},"error_message":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"}}},"AnalyticsAccount":{"type":"object","title":"Analytics account","properties":{"id":{"type":"integer"},"platform":{"type":"string"},"username":{"type":"string"},"avatar_url":{"type":["string","null"]},"connected":{"type":"boolean"},"summary":{"oneOf":[{"$ref":"#/components/schemas/AnalyticsSummary"},{"type":"null"}]},"alerts":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"AnalyticsSummary":{"type":"object","title":"Analytics summary","properties":{"total_views":{"type":"integer"},"total_likes":{"type":"integer"},"total_comments":{"type":"integer"},"total_shares":{"type":"integer"},"total_favorites":{"type":"integer"},"engagement_rate":{"type":"number","description":"Likes, comments and shares over views, as a percentage."},"tracked_posts_count":{"type":"integer"},"reelmoney_posts_count":{"type":"integer"},"last_refreshed_at":{"type":["string","null"],"format":"date-time"},"refresh_status":{"type":["string","null"]},"can_refresh":{"type":"boolean"},"seconds_until_refresh":{"type":["integer","null"]}}},"AccountAnalytics":{"type":"object","title":"Account analytics","properties":{"account":{"$ref":"#/components/schemas/AnalyticsAccount"},"summary":{"oneOf":[{"$ref":"#/components/schemas/AnalyticsSummary"},{"type":"null"}]},"posts":{"type":"array","items":{"$ref":"#/components/schemas/PostMetric"}},"period":{"type":"string","enum":["24h","7d","30d","90d"]},"filter":{"type":"string","enum":["all","reelmoney"]},"sort":{"type":"string"},"dir":{"type":"string","enum":["asc","desc"]},"platform_supported":{"type":"boolean"},"supported_platforms":{"type":"array","items":{"type":"string"}}}},"PostMetric":{"type":"object","title":"Post metric","properties":{"id":{"type":"integer"},"external_post_id":{"type":["string","null"]},"title":{"type":["string","null"]},"view_count":{"type":"integer"},"like_count":{"type":"integer"},"comment_count":{"type":"integer"},"share_count":{"type":"integer"},"favorite_count":{"type":"integer"},"engagement_rate":{"type":["number","null"]},"cover_image_url":{"type":["string","null"]},"external_url":{"type":["string","null"]},"post_description":{"type":["string","null"]},"duration_seconds":{"type":["integer","null"]},"posted_at":{"type":["string","null"],"format":"date-time"},"fetched_at":{"type":["string","null"],"format":"date-time"},"is_reelmoney":{"type":"boolean","description":"True when ReelMoney published this post."},"platform":{"type":"string"}}}}}}