Skip to content

API reference

Generate videos, check status and manage renders over REST.

Base URL

https://reeloop.ai/api/v1

All endpoints are JSON. Authenticate every request with a Bearer token (see Authentication). Errors use the standard envelope { "error": { "code", "message" } } and every response carries X-RateLimit-Limit/Remaining/Reset headers.

Generate a video

POST /api/v1/generate - two explicit modes. Omit mode to use the default ("director"); passing any value other than "instant" or "director" returns a 400 invalid_mode error:

  • Director Mode (default) - generates a free draft (script + shot plan) and parks the job at awaiting_approval. No credits are used yet. You review the draft, then approve it to start the paid render.
  • Instant Mode - pass "mode": "instant" to charge credits and start the render immediately (webhook/automation flows).
curl -X POST https://reeloop.ai/api/v1/generate \
  -H "Authorization: Bearer fl_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "topic": "5 facts about the ocean nobody knows",
    "style": "documentary",
    "voice": "charlotte",
    "language": "en",
    "duration": "30",
    "mode": "instant"
  }'

Director Mode response (0 credits consumed):

{
  "jobId": "7c9e2a1d-…",
  "mode": "director",
  "status": "awaiting_approval",
  "draft": { "hook": "…", "slides": […], "shots": […], "creditCost": 3 }
}

Instant Mode response:

{ "jobId": "7c9e2a1d-…", "status": "processing" }

Choose a video engine (optional)

By default every video renders on Reeloop's standard engine at no extra cost. Paid plans can opt into a higher-fidelity engine by passing videoModel with a registry id:

{ "topic": "…", "videoModel": "seedance-2.5" }

GET /api/account/plan returns videoFeature.models - the exact ids your account can currently select, each with its resolution (480p/720p/1080p) and creditsPer30s rate; credits scale with the chosen engine, so check that response rather than hardcoding a rate. videoModel only applies to a plain video (no talking-avatar, no UGC mode, no 4K, no Scale/Hybrid videoMode) - combining it with any of those returns 400 model_not_applicable. Omit the field entirely for the default engine, unchanged from before this field existed.

Approve or reject a draft (Director Mode)

POST /api/v1/jobs/{jobId}/approve - approve a draft parked at awaiting_approval. This charges the draft's credit cost and starts the render. Optionally pass draft edits in the body:

{ "hook": "…", "voiceoverScript": "…", "shots": […] }

Response:

{ "jobId": "7c9e2a1d-…", "status": "processing" }

Approving from the dashboard review UI works identically - same endpoint family. Editing voiceoverScript (or pronunciations) re-synthesizes the voiceover - and therefore the captions - to match; an untouched script reuses the draft's cached audio for free. The body also accepts slides/shots (index-coupled - resulting lengths must match, 1-18 scenes), pronunciations (respellings spoken by the TTS only), and captionStyle.

POST /api/v1/jobs/{jobId}/reject - discard a draft parked at awaiting_approval. No credit was ever consumed, so there is nothing to refund; the job simply closes.

Edit a draft before approving (free, versioned)

GET /api/v1/status/{jobId} on an awaiting_approval job returns the editable draft plus draftRevision - the optimistic-concurrency cursor for saves.

PATCH /api/v1/jobs/{jobId}/draft - persist edits as the draft's next revision without approving. Free - never triggers TTS. Body: { "baseRevision": 1, "edits": { "hook": "…", "voiceoverScript": "…", "slides": […], "shots": […], "pronunciations": […] } }. A stale baseRevision returns 409 revision_conflict with the server's currentRevision to re-base on. Supports Idempotency-Key.

GET /api/v1/jobs/{jobId}/draft/revisions - the append-only version history (newest first); ?revision=N returns one revision's full payload.

Export captions and script

GET /api/v1/jobs/{jobId}/captions?format=srt|vtt - a subtitle file assembled from the stored word-timing transcript. Free. Timings approximate the burned-in captions (the renderer runs its own transcription pass on the audio). 410 when the transcript predates permanent retention.

GET /api/v1/jobs/{jobId}/script - the approved script, per-scene prompts, style bible and pronunciation map as JSON. Free.

Check render status

GET /api/v1/status/{jobId} - poll until renderStatus is done or failed.

{
  "jobId": "7c9e2a1d-…",
  "status": "completed",
  "renderStatus": "done",
  "finalVideoUrl": "https://…/media/….mp4",
  "videoUrls": ["https://…/scene-1.mp4", "…"],
  "aiGenerated": true,
  "provenance": { "contentId": "…", "aiGenerated": true, "marking": "…" }
}

The two state fields (read this before integrating)

The API exposes two state machines, and you need both:

FieldValuesMeaning
statusawaiting_approval, processing, completed, failedThe JOB lifecycle. completed only means "no longer running" - not that a video exists.
renderStatusnull, "rendering", "done", "failed"The RENDER pipeline outcome. This is the field to check for the video.

The trap: a render whose final assembly fails ends with status: "completed" and renderStatus: "failed" (credits refunded automatically). Treat the video as ready only when renderStatus === "done" and finalVideoUrl is non-null. Every other combination means "keep waiting" or "it failed".

Failed render example:

{
  "jobId": "7c9e2a1d-…",
  "status": "completed",
  "renderStatus": "failed",
  "finalVideoUrl": null
}

Generation runs in the background (a finished short is typically ready within a few minutes - real measured demos run ~3-4.5 min). Prefer webhooks over polling for production use.

AI-content marking (EU AI Act, Art. 50)

Every finished video is marked as AI-generated, in layers, to improve detectability across downloads and publishing workflows:

  • Embedded in the MP4 - an XMP packet in a udta box declares aiGenerated="true" plus a stable contentId. Readable with ffprobe/exiftool/any XMP parser, and it survives storage and direct download.
  • In the API - the status payload returns aiGenerated: true and a provenance block (contentId, marking).
  • At publish time - platform-native "AI-generated" disclosure toggles.

To verify a file you downloaded:

exiftool -xmp -b video.mp4        # shows aiGenerated="true" + the contentId

What each layer does: the downloaded MP4 carries the embedded XMP marker; the API status response carries the provenance fields; and the native AI-generated flag is sent to the platform at publish time. Be aware that a platform which re-encodes your upload (transcodes it) can strip the file-level XMP metadata - which is exactly why the marking is layered rather than only in the file. Signed C2PA Content Credentials remain on the roadmap.

List videos

GET /api/v1/videos?limit=20&cursor=<ISO timestamp> - cursor-paginated list of your library, newest first.

Publish

POST /api/v1/publish - publish a finished video to your connected accounts:

{ "jobId": "7c9e2a1d-…", "platforms": ["youtube", "tiktok", "instagram"], "caption": "…" }

Start building

Create an API key in Settings → API.

Start for free

1 free video · no credit card