ClipMaxxing is now in open beta. Credit costs are 20% off.

Building on this API?

Point your AI at this page (or send it the raw spec). It has everything needed to integrate ClipMaxxing: auth, endpoints, and copy-paste examples.

ClipMaxxing Public API v1

This document is the integration spec for the ClipMaxxing HTTP API. Give it to an AI or a developer that needs to generate vertical shorts (voiceover, captions, or AI video) from a bot, Discord app, or backend.

  • Human-readable page: https://clipmaxxing.app/docs
  • Raw markdown (this file): https://clipmaxxing.app/docs/api.md
  • Product: https://clipmaxxing.app

ClipMaxxing turns a story (or a prompt) plus a background clip into a 1080x1920 MP4 with optional AI voiceover and word-synced captions. Billing uses the same credit wallet as the website. There is no separate API plan.

1. Base URL and authentication

EnvironmentBase URL
Productionhttps://clipmaxxing.app/api
Local API (direct)http://localhost:3000

All v1 paths in this document are relative to the API origin. In production, that origin is https://clipmaxxing.app/api (the site reverse-proxies /api to the Fastify server). Locally, call port 3000 with no /api prefix.

Authenticate every request with the header:

x-api-key: cmx_<48 hex characters>

Example:

GET /v1/projects HTTP/1.1
Host: clipmaxxing.app
x-api-key: cmx_...

Rules:

  • Create and revoke keys in the website: Account → API keys (https://clipmaxxing.app/account).
  • The full key is shown once at creation. Store it like a password.
  • A key is bound to one user. Credits, library, and projects are that user's.
  • An API key cannot create or list other API keys. That requires a website session cookie.
  • Send JSON with Content-Type: application/json on POST bodies.
  • Do not send a session cookie from a bot. Header auth is enough.

2. Typical bot flow

Generation is asynchronous. One POST starts a job. Poll until READY or FAILED, then download the MP4.

1. (optional) GET /v1/library/videos   → pick libraryVideoId
2. (optional) GET /v1/voices           → pick voiceId (storytelling only)
3. POST /v1/generate/<flow>            → { projectId, status: "GENERATING" }
4. GET  /v1/projects/{id}              → poll every 3–5 seconds
5. GET  /v1/projects/{id}/download     → MP4 (ZIP for carousels) when status is READY

Rate limit on generate POSTs: 10 requests per minute per IP. Other v1 GETs use the global IP limit.

Recommended poll interval: 3 to 5 seconds. Jobs can take from about 30 seconds to several minutes (queue + TTS + FFmpeg, or AI video).

3. Endpoints

POST /v1/generate/storytelling

Creates a project and starts the pipeline: background video + spoken story + karaoke captions.

Provide either story (final voiceover script) or prompt (ClipMaxxing writes the script with the LLM, billed separately). Provide either libraryVideoId or videoUrl or brandId for the B-roll.

FieldTypeRequiredDefaultNotes
storystringone of story/promptFinal voiceover script.
promptstringone of story/promptBrief, 3–4096 characters. Ignored if story is set.
libraryVideoIdcuidone of libraryVideoId/videoUrl/brandIdId from GET /v1/library/videos.
videoUrlURLone of libraryVideoId/videoUrl/brandIdDirect HTTPS MP4. ClipMaxxing downloads it in a stream. Do not send both this and libraryVideoId.
brandIdcuidone of libraryVideoId/videoUrl/brandIdTemplate id. Inherits kit + default B-roll tags.
langenumnofrfr, en, es, de, it, pt
targetDurationSecintno6010, 15, 30, 45, 60, 90
titlestringnoMax 200 characters.
voiceIdstringnolanguage defaultId from GET /v1/voices.
captionStyleenumnoyellow-highlightSee caption styles below.
captionPositionenumnobottomtop, center, bottom, none
storyPresetenumnostandardstandard or horror
libraryMusicIdcuidnoOptional bed music from the music library.

Success 200:

{ "projectId": "clxxxxxxxxxxxxxxxxxxxxxxxxx", "status": "GENERATING" }

POST /v1/generate/text-scene

Same B-roll source rules as storytelling (libraryVideoId or videoUrl, story or prompt). No voiceover. Overlay text on the clip.

FieldTypeRequiredDefaultNotes
story / promptstringone of themOverlay copy. Keep it short (best under 400 characters).
libraryVideoId / videoUrl / brandIdone of themSame as storytelling.
langenumnofr
targetDurationSecintno60Same duration set as storytelling.
titlestringno
captionPositionenumnobottom
libraryMusicIdcuidno

Response: same { projectId, status }.

POST /v1/generate/video

AI video. No background clip required. Default: silent 9:16 540p text-to-video.

FieldTypeRequiredDefaultNotes
promptstringyes3–5000 characters.
langenumnoen
targetDurationSecintno10Integer from 4 to 15.
titlestringno
generationSettingsobjectnosee below

Default generationSettings:

{
  "mode": "text-to-video",
  "quality": "540p",
  "aspectRatio": "9:16",
  "generateAudio": false,
  "multiClip": false,
  "imageIds": []
}

mode values: text-to-video, image-to-video, reference-to-video. quality: 540p or 720p. aspectRatio: 16:9 or 9:16 (required for text-to-video and reference-to-video). Public v1 does not upload reference images. Stay on text-to-video unless you already have imageIds from the website.

Response: same { projectId, status }.

POST /v1/generate/carousel

Photo carousel (swipeable image post). No background clip, no voiceover. The deliverable is a ZIP of cards, not an MP4.

This is the endpoint to replay a template with a different brief: pass brandId plus the brief, and the template supplies the language, the number of images and the visual template. Anything you pass in the body wins over the template.

FieldTypeRequiredDefaultNotes
briefstringyesWhat the carousel is about, 12–5000 characters. Internal instruction: it is never published as the caption.
brandIdcuidnoTemplate id. Supplies lang, slideCount, referenceImageId, the kit and the publishing accounts.
langenumnotemplate, else frfr, en, es, de, it, pt
titlestringnoMax 200 characters.
slideCountintnotemplate, else autoInteger from 2 to 10. Auto lets the model pick 4 to 8.
referenceImageIdcuidnotemplateVisual template photo. Ids come from the website upload.

Response: same { projectId, status }. GET /v1/projects/{id}/download then returns application/zip.

POST /v1/generate/carousel-manual

Same ZIP of vertical cards as /v1/generate/carousel, but you write each card. No LLM plans the text. Each slide is 50% photo / 50% HTML (sanitized: no scripts, no images, no url() in CSS).

FieldTypeRequiredDefaultNotes
slidesobject[]yes2–10 cards. See below.
brandIdcuidnoTemplate id. Supplies lang, visual template photo, and the accent color used on the category chip.
langenumnotemplate, else frfr, en, es, de, it, pt
titlestringnoMax 200 characters.
referenceImageIdcuidnotemplateVisual template photo for AI slides. Ids come from POST /images.

Each slides[] item:

FieldTypeRequiredNotes
htmlstringyesCard layout you write. Tags: p br hr strong b em i u s small mark sub sup ul ol li h1 to h6, span div section article header footer aside main blockquote table thead tbody tfoot tr th td caption. Inline style may set layout and type (display / flex / grid / position except fixed, sizes, padding, colors hex/rgb/rgba/hsl). No scripts, images, url(), content, or event handlers. Max 4000 characters.
imagePromptstringone ofEnglish prompt for the photo (min 3 chars). Required unless sourceImageId is set.
sourceImageIdcuidone ofUploaded JPEG/PNG/WebP (POST /images). Mutually exclusive with imagePrompt.
categorystringnoPlain text, max 32. Drawn as a chip on the right, on the photo/text split. Color comes from the template accent.

Response: same { projectId, status }. Download is application/zip. Billing uses the same CREDIT_CAROUSEL_IMAGE as the classic carousel, only for slides without an upload.

POST /v1/generate/carousel-custom

Same ZIP of vertical cards, but you write the whole card layout, not just its text half. Each card is your HTML rendered at 1080×2400 (9:20, mobile). Exactly one element must carry id="placeholder": the generated (or uploaded) photo is inserted into it, scaled to cover the slot, and cropped to that slot's rendered shape (border-radius, including a circle, or clip-path). Flat margins on the source are trimmed first, so a square photo does not leave pale bars inside the visible area. No src is ever accepted from you.

FieldTypeRequiredDefaultNotes
slidesobject[]yes2–10 cards. See below.
brandIdcuidnoTemplate id. Supplies lang and the visual template photo.
langenumnotemplate, else frfr, en, es, de, it, pt
titlestringnoMax 200 characters.
referenceImageIdcuidnotemplateVisual template photo. Ids come from POST /images.

Each slides[] item:

FieldTypeRequiredNotes
htmlstringyesFull card layout. Same tag and inline-style allowlist as carousel-manual, plus figure figcaption dl dt dd and gradient background-image. The only attributes kept are the filtered style and a single id="placeholder". No scripts, images, url(), or event handlers. Max 12000 characters.
imagePromptstringone ofEnglish prompt for the photo (min 3 chars). Required unless sourceImageId is set.
imageAspectenumno9:16 (default), 3:4, 1:1, 4:3, 16:9. Pick the ratio closest to your slot.
sourceImageIdcuidone ofUploaded JPEG/PNG/WebP (POST /images). Mutually exclusive with imagePrompt.

Fonts available in the card: Montserrat, Inter, Anton, Playfair Display, Space Mono. Keep text clear of the TikTok UI: the top 400 px, the bottom 560 px and ~160 px on each side.

Response: same { projectId, status }. Download is application/zip. Billing uses the same CREDIT_CAROUSEL_IMAGE as the classic carousel, only for slides without an upload.

GET /v1/projects?limit=10

Lists the caller's latest projects, newest first. limit is an integer from 1 to 10 (default 10).

[
  {
    "id": "clxxx...",
    "title": "My short",
    "videoStyle": "storytelling",
    "status": "READY",
    "createdAt": "2026-08-23T12:00:00.000Z",
    "downloadUrl": "https://clipmaxxing.app/api/v1/projects/clxxx.../download"
  }
]

downloadUrl is null until the latest version is READY. Prefer GET /v1/projects/{id}/download with the same x-api-key header.

GET /v1/projects/:id

{
  "id": "clxxx...",
  "status": "GENERATING",
  "steps": [
    { "label": "Script", "status": "DONE" },
    { "label": "Voice", "status": "RUNNING" },
    { "label": "Video ready", "status": "PENDING" }
  ],
  "queuePosition": 1,
  "downloadUrl": null,
  "error": null
}
  • status on the project: DRAFT, GENERATING, READY, FAILED.
  • Each step status: PENDING, RUNNING, DONE, FAILED.
  • queuePosition is 0 unless the job is waiting in the queue (QUEUED). Then it is 1-based.
  • error is a string when the latest version failed, otherwise null.
  • Stop polling when status is READY (download) or FAILED (read error). Do not retry a failed project automatically without a new generate call.

GET /v1/projects/:id/download

Streams the MP4 of the latest READY version.

  • Headers: x-api-key (required).
  • Response: Content-Type: video/mp4, Content-Disposition: attachment.
  • 404 if the project is missing, not yours, or not ready yet.

GET /v1/library/videos

Published B-roll clips you can pass as libraryVideoId.

{
  "enabled": true,
  "videos": [
    { "id": "clxxx...", "title": "City night", "durationSec": 12.4, "tags": ["urban"] }
  ]
}

If enabled is false, the library is not configured on that instance. Use videoUrl instead.

GET /v1/voices

Voice catalog for voiceId (storytelling). Each item: { id, name, languages } where languages is a subset of fr, en, es, de, it, pt.

GET /v1/brands

Templates of the key's user. Response is a JSON array (not wrapped). The webhook secret is never returned (webhookConfigured: true|false instead). Each template includes attached accounts (id, platform, alias, label, state).

Pass brandId on generate endpoints to inherit voice, captions, illustration style, B-roll tags, and narration tone. The applied kit is frozen on the ProjectVersion (brandKit). Replaying a version does not re-read the current template. Editing a template does not change jobs already generated.

GET /v1/brands/:id

One template, same object as a list item. 404 if the id is unknown or belongs to another user.

POST /v1/brands

Creates a template. Rate limit: 10 requests per minute per IP.

Only name is required. Everything else is optional and can be filled later with PATCH. Linking YouTube / TikTok / Instagram accounts is done on the website (OAuth). The accounts array on the response is empty until then.

FieldTypeRequiredDefaultNotes
namestringyes1–60 characters. Unique per user. 409 if taken.
colorhexno#6750A4Accent, #RRGGBB.
sortOrderintno00–999. List order.
descriptionstringnoMax 500 characters.
langenumnofr, en, es, de, it, pt.
videoStyleenumnostorytelling, text-scene, video-generation, carousel, carousel_manual, carousel_custom.
storyPresetenumnostandard or horror (storytelling script only).
targetDurationSecintno10, 15, 30, 45, 60, 90.
voiceIdstringnoId from GET /v1/voices.
captionStyleenumnoSee caption styles on generate.
captionPositionenumnotop, center, bottom, none.
promptAddonstringnoBrand voice clause injected into the LLM. Max 2000. Stay positive.
illustrationStylestringnoPositive image-style suffix. Max 400.
slidesDirectivesstringnoMax 500.
palettehex[]noUp to 8 #RRGGBB.
settingHintstringnoTime of day, weather, light. Max 220.
castobject[]noUp to 8 { name, lock }.
titleTemplatestringnoPublish title. {title} is replaced. Max 100.
captionTemplatestringnoPublish caption. {story} is a short excerpt. Max 2200.
hashtagsstringnoAppended to the caption. Max 400.
carouselSlideCountint or nullno2–10. null clears: Auto (4–8).
carouselReferenceImageIdcuid or nullnoVisual template photo. Upload with POST /images (same x-api-key, multipart field file). null clears. 404 if the image is not yours.
reviewRequiredboolnotrue
libraryVideoTagsstring[]no[]Up to 12 tags. B-roll fallback when generate has no clip.
defaultLibraryVideoIdcuid or nullnoPreferred library clip. Id from GET /v1/library/videos.
musicCatalogIdstring or nullnoBuilt-in bed music catalog id.
sourceAudioIdcuid or nullnoUser-uploaded bed.
webhookUrlURL or nullnoSee webhooks below. Max 500.
webhookSecretstring or nullnoWrite-only, 8–128 characters. Never returned.

Success 200: the created template (same shape as GET /v1/brands/:id).

PATCH /v1/brands/:id

Partial update. Send at least one field (same keys as POST). Empty body is 400. Rate limit: 10 / minute. 404 if unknown. 409 if the new name is taken.

Changing the kit does not rewrite brandKit on versions already generated.

DELETE /v1/brands/:id

Deletes the template. Linked social accounts are not deleted: they become ungrouped. Existing projects keep their frozen kit. 404 if unknown. Returns { "ok": true }.

GET /v1/review

Projects in READY + reviewStatus: PENDING. Filter later client-side by brandName.

POST /v1/projects/:id/approve

Body (all optional): { "mode": "queue", "scheduledAt": "...", "accountIds": ["..."] }.

Default mode is queue (next posting slot). now publishes immediately (TikTok still drops in the inbox unless Direct Post is enabled). Omit accountIds to use every ACTIVE YouTube / TikTok / Instagram account on the project's template.

Title and caption come from the frozen kit templates ({title}, {story}, hashtags).

POST /v1/projects/:id/reject

Marks the project REJECTED. It leaves the review queue. Does not delete the MP4.

POST /v1/posts

Same body as the website composer: { versionId, targets: [{ accountId }], mode, title?, caption?, scheduledAt? }. mode: now | queue | at | draft.

POST /v1/generate/batch

{ "brandId": "...", "prompts": ["...", "..."] } (1 to 10 prompts). Each prompt starts a storytelling job with the template kit and B-roll. Returns { projectIds, status: "GENERATING" }. Stops on the first failure (credits, missing B-roll).

POST /v1/ingest

Finished MP4, no AI pipeline. { "brandId": "...", "videoUrl": "https://...", "title": "optional" }. Creates a READY project in the review queue. Used by Discord /drop.

GET /v1/inbox

TikTok drops waiting in the creator inbox (uploadToInbox). Open the TikTok app to publish. Public TikTok posting is not automated.

GET /v1/projects?limit=10&brandId=&review=

review is PENDING | APPROVED | REJECTED. brandId filters by template.

3b. Webhooks (per template)

Set webhookUrl (and optionally webhookSecret) on the template. ClipMaxxing POSTs JSON:

{
  "event": "project.ready",
  "projectId": "clxxx...",
  "brandId": "clxxx...",
  "brandName": "Horror FR",
  "title": "...",
  "status": "READY",
  "reviewStatus": "PENDING",
  "downloadUrl": "https://clipmaxxing.app/api/v1/projects/clxxx.../download"
}

Events: project.ready, project.failed, project.approved, project.rejected.

If a secret is set, header X-ClipMaxxing-Signature: sha256=<hex> (HMAC-SHA256 of the raw body). A failing webhook never fails the pipeline.

Point webhookUrl at a Discord incoming webhook for a ping in #review-horror, or at your own bot. Never put CLIPMAXXING_API_KEY in a Discord message.

3c. Discord bot (apps/discord)

Two audiences, one process. Env: DISCORD_BOT_TOKEN, DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET (OAuth), DISCORD_BOT_SECRET (API impersonation), CLIPMAXXING_API_URL, CLIPMAXXING_API_KEY (editorial only). Optional: DISCORD_STAFF_GUILD_ID, DISCORD_ADMIN_IDS. Run npm run dev:discord.

Link Discord on Account → Connect Discord. The bot DMs you; if that is blocked, open a DM and type /start. Credits are billed to the linked ClipMaxxing user (x-discord-bot-secret + x-discord-user-id). Never put CLIPMAXXING_API_KEY in a Discord message.

Client commands (DM / user install)

CommandAction
/startWizard: voice-over short, text on B-roll, or AI video
/short flow: text:One-shot generate (optional video / image attachments)
/statusLatest projects and credit balance
/cancelAbort the wizard (not a running worker job)

When the render is ready, the bot attaches the MP4 (if small) or a 1-hour download link (/d/:token).

Editorial commands

With DISCORD_STAFF_GUILD_ID they are guild-only. CLIPMAXXING_API_KEY is still the editorial auth; staff does not need to link Discord.

CommandAction
/generate template: prompt:Storytelling with the template kit
/drop template: video:Ingest a finished MP4 into review
/reviewQueue + Approve / Reject buttons
/schedule template:Approve latest pending into the posting queue
/inboxTikTok inbox drops
/templatesList templates
/linesAlias of /templates

TikTok public posting is never automated. Approve still drops to the TikTok inbox.

4. Caption styles

For captionStyle on storytelling:

  • yellow-highlight (default)
  • kinetic
  • neon-outline
  • boxed
  • karaoke
  • red-marker
  • mono-terminal
  • luxury-serif
  • impact-stroke

captionPosition: top, center, bottom (default), none.

5. Credits and errors

Every generation holds credits on the user wallet (same as the website), then settles the real cost when the pipeline finishes. If the balance is too low, the call fails before the job is queued.

Error body (all endpoints):

{ "error": "Human-readable message" }
HTTPMeaning
400Invalid JSON / Zod validation / missing source or story.
401Missing, unknown, or revoked API key.
402Not enough credits. Body may include required, balance, code.
404Project, clip, template, or file not found.
409A generation is already running, or a template already has that name.
422LLM refused the prompt (policy).
429Rate limit. Wait about one minute.
502Upstream (download, library cache, model) failed. Retry later.
503Queue, billing, library, or provider not configured.

Validation messages look like field : message plus an issues array.

6. Copy-paste examples

Replace BASE and KEY.

Storytelling from a library clip + prompt

curl -sS -X POST "$BASE/v1/generate/storytelling" \
  -H "x-api-key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A 60-second story about a baker who finds a golden coin in the dough.",
    "libraryVideoId": "REPLACE_WITH_CLIP_ID",
    "lang": "en",
    "targetDurationSec": 60
  }'

Storytelling from a public MP4 URL + finished script

curl -sS -X POST "$BASE/v1/generate/storytelling" \
  -H "x-api-key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "story": "You will not believe what happened in that bakery tonight. ...",
    "videoUrl": "https://example.com/broll.mp4",
    "lang": "fr",
    "targetDurationSec": 45,
    "voiceId": "REPLACE_WITH_VOICE_ID",
    "captionStyle": "karaoke"
  }'

Poll then download

PROJECT_ID="clxxx..."

while true; do
  BODY=$(curl -sS -H "x-api-key: $KEY" "$BASE/v1/projects/$PROJECT_ID")
  STATUS=$(printf '%s' "$BODY" | sed -n 's/.*"status":"\([^"]*\)".*/\1/p' | head -n1)
  echo "$BODY"
  if [ "$STATUS" = "READY" ]; then
    curl -sS -H "x-api-key: $KEY" \
      -o "short.mp4" \
      "$BASE/v1/projects/$PROJECT_ID/download"
    break
  fi
  if [ "$STATUS" = "FAILED" ]; then
    echo "Generation failed"
    exit 1
  fi
  sleep 4
done

Text-scene

curl -sS -X POST "$BASE/v1/generate/text-scene" \
  -H "x-api-key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "story": "Open tonight. Last table.",
    "libraryVideoId": "REPLACE_WITH_CLIP_ID",
    "targetDurationSec": 10,
    "lang": "en"
  }'
BRAND=$(curl -sS -X POST "$BASE/v1/brands" \
  -H "x-api-key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Breakfast carousel",
    "lang": "en",
    "videoStyle": "carousel",
    "carouselSlideCount": 5,
    "titleTemplate": "{title}",
    "captionTemplate": "{story}",
    "hashtags": "#breakfast #highprotein"
  }')
echo "$BRAND"
BRAND_ID=$(printf '%s' "$BRAND" | sed -n 's/.*"id":"\([^"]*\)".*/\1/p' | head -n1)

curl -sS -X POST "$BASE/v1/generate/carousel" \
  -H "x-api-key: $KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"brandId\": \"$BRAND_ID\",
    \"brief\": \"5 quick high-protein breakfasts, one dish photo per card with the macros under it\"
  }"

PATCH /v1/brands/$BRAND_ID with { "carouselSlideCount": 6 } updates later jobs only.

To set a visual template photo, upload first (POST /images, multipart file), then PATCH carouselReferenceImageId with the returned id.

curl -sS -X POST "$BASE/v1/generate/carousel" \
  -H "x-api-key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "brandId": "REPLACE_WITH_TEMPLATE_ID",
    "brief": "5 quick high-protein breakfasts, one dish photo per card with the macros under it"
  }'

The same call with another brief reuses the template's visual template, image count and language.

curl -sS -X POST "$BASE/v1/generate/carousel-manual" \
  -H "x-api-key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "brandId": "REPLACE_WITH_TEMPLATE_ID",
    "lang": "en",
    "slides": [
      {
        "html": "<h1>Overnight oats</h1><p>150 g oats</p>",
        "imagePrompt": "overhead photo of overnight oats in a glass jar",
        "category": "Breakfast"
      },
      {
        "html": "<h1>Eggs</h1><p>2 eggs, 1 tsp oil</p>",
        "imagePrompt": "sunny-side eggs in a small skillet"
      }
    ]
  }'

imagePrompt and sourceImageId are mutually exclusive per slide. The template color is the category chip background.

curl -sS -X POST "$BASE/v1/generate/carousel-custom" \
  -H "x-api-key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "lang": "en",
    "slides": [
      {
        "html": "<div style=\"display:flex;flex-direction:column;height:100%;background-color:#101018\"><div id=\"placeholder\" style=\"height:1400px\"></div><h1 style=\"font-family:Montserrat;font-size:86px;color:#fff;padding:0 160px\">Overnight oats</h1></div>",
        "imagePrompt": "overhead photo of overnight oats in a glass jar",
        "imageAspect": "1:1"
      },
      {
        "html": "<section style=\"position:relative;height:100%\"><div id=\"placeholder\" style=\"position:absolute;inset:0\"></div><p style=\"position:absolute;bottom:560px;left:160px;font-size:48px;color:#fff\">2 eggs, 1 tsp oil</p></section>",
        "imagePrompt": "sunny-side eggs in a small skillet"
      }
    ]
  }'

The photo goes inside id="placeholder"; anything else you put in that element stays on top of it.

AI video (text-to-video)

curl -sS -X POST "$BASE/v1/generate/video" \
  -H "x-api-key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Silent vertical shot of rain on neon pavement, cinematic, loopable, no text",
    "lang": "en",
    "targetDurationSec": 8
  }'

7. Integration notes for AIs and bots

  • Treat this file as the source of truth. Do not invent extra v1 endpoints.
  • One-shot generate endpoints already create the project and enqueue the worker. Do not call the website's /projects then /projects/:id/generate from a bot.
  • Always send x-api-key on the download request. A bare URL without the header returns 401.
  • videoUrl must be a publicly reachable MP4 (h264/hevc/vp8/vp9/av1/mpeg4). Huge files may fail the instance upload cap.
  • If you pass prompt without story, ClipMaxxing bills a story-generation LLM call first, then the pipeline.
  • Horror preset (storyPreset: "horror") only applies to storytelling script generation. No gore.
  • brandId on generate endpoints inherits the template kit (voice, captions, illustration style, B-roll tags). The applied kit is frozen on the version.
  • Provide either libraryVideoId or videoUrl or brandId (brand B-roll from default clip / library tags).
  • GET /v1/brands, GET /v1/brands/:id, POST /v1/brands, PATCH /v1/brands/:id, DELETE /v1/brands/:id, GET /v1/review, POST /v1/projects/:id/approve, POST /v1/projects/:id/reject, POST /v1/posts, POST /v1/generate/batch, POST /v1/ingest, GET /v1/inbox exist for bots. GET /v1/brands returns a raw array. Do not call the website's /account-groups from a bot.
  • Approve queues YouTube / Instagram and drops TikTok in the inbox. There is no public TikTok post without Direct Post audit.
  • Illustration prompts stay positive. Do not send “no comic strip” style negatives; z-image has no negative_prompt.
  • Projects expire after the product retention window (default 5 days on the website). Download promptly.
  • Do not store the API key in client-side Discord messages. Use a server-side bot token store.

8. Key management (website only, not v1)

These routes require a logged-in browser session, not x-api-key:

  • GET /api-keys : list { id, name, prefix, createdAt, lastUsedAt }
  • POST /api-keys { "name": "Discord bot" } : returns { id, key, prefix } once
  • DELETE /api-keys/:id : revoke (soft)

Key format: cmx_ + 48 lowercase hex characters. Prefix (first 8 characters) is what the profile UI shows.