Machine-readable discovery

Use llms.txt and openapi.json for agent discovery and contract parsing.

Naming note

The endpoint and payload are recipe-first: use https://api.reelforger.com/v1/recipes/render with recipe_id.

API Reference

For MCP callers, use the thin wrapper documented at /docs/mcp-remote-tools. MCP V1 mirrors these endpoints; it does not introduce a second planning/runtime layer.

For a dedicated guide to asynchronous music generation, polling, cost behavior, and reusing returned track URLs in a render, see /docs/music-api.

POST https://api.reelforger.com/v1/recipes/render (Recipes API, canonical)

Recommended default for AI-agent workflows. Send a strict recipe request and ReelForger compiles it into a full manifest, enqueues the job, and returns a job_id.

Caption input behavior for recipes:

  • If variables.transcript_words is supplied, ReelForger uses it directly.
  • If variables.transcript_words is omitted, ReelForger may transcribe automatically.
  • If source selection is ambiguous, set variables.transcription_source_asset_id.

Parameters

NameTypeRequiredDescription
Authorization (header)stringRequiredBearer <API_KEY>
idempotency-key (header)stringOptionalRequest dedupe key (header takes precedence)
recipe_idenumRequired (canonical)Recipe family identifier (for example voiceover_explainer)
style_presetenumRequiredCaption/style preset for generated overlays and captions
variablesobjectRequiredStrict per-recipe variables object
variables.transcript_wordsarrayOptionalIf supplied, ReelForger uses these words directly for caption timing
variables.transcription_source_asset_idstringOptionalExplicit caption transcription source when transcript words are omitted (required if source is ambiguous)
webhook_urlstring(url)OptionalPublic endpoint that accepts webhook POST events
webhook_headersobjectOptionalOptional custom webhook headers
webhook_secretstringOptionalOptional per-request webhook signing override
idempotency_keystringOptionalPayload-level dedupe key

Request Example (cURL)

curl -X POST "https://api.reelforger.com/v1/recipes/render" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -d '{
    "recipe_id": "voiceover_explainer",
    "style_preset": "karaoke_yellow",
    "idempotency_key": "recipe-001",
    "variables": {
      "voiceover_audio_url": "https://samplelib.com/lib/preview/mp3/sample-15s.mp3",
      "background_assets": ["https://picsum.photos/id/1018/1080/1920"]
    }
  }'

Notes for caption-capable recipes:

  • Add variables.transcript_words when you already have timestamps and want ReelForger to use them directly.
  • Omit variables.transcript_words to let ReelForger transcribe automatically.
  • Use variables.transcription_source_asset_id if source selection is ambiguous.
  • You can still provide correct_text where supported to polish punctuation/casing even when ReelForger auto-transcribes words.
  • Current behavior: auto-transcription bills and processes the full selected source duration sent for transcription (not a smart-trimmed subsection).
  • Transcription billing rule: 1 credit per 5 seconds transcribed (rounded up), charged only when ReelForger successfully performs transcription.

Response Example (202 Accepted)

{
  "success": true,
  "job_id": "8f1fd0fe-63a5-4fef-8f2c-8f1225f6d309",
  "request_id": "req_abc123"
}

POST https://api.reelforger.com/v1/music/generate

Creates an asynchronous music generation job using the public API.

Important behavior:

  • Every successful music generation costs exactly 20 credits.
  • Every successful music generation returns exactly 2 distinct tracks.
  • Those returned audio_url values can be used directly in later ReelForger render requests as normal audio assets.

Parameters

NameTypeRequiredDescription
Authorization (header)stringRequiredBearer <API_KEY>
idempotency-key (header)stringOptionalRequest dedupe key
promptstringRequired in simple modePrompt for the generation request
custom_modebooleanOptionalDefaults to false
instrumentalbooleanOptionalDefaults to true
modelenumOptionalV4 | V4_5 | V4_5PLUS | V4_5ALL | V5 (defaults to V5)
stylestringRequired in custom modeGenre / mood / production direction
titlestringRequired in custom modeBase title for the returned tracks
negative_tagsstringOptionalExclusions for the generation
webhook_urlstring(url)OptionalPublic HTTPS endpoint that accepts music job webhook POSTs
webhook_headersobjectOptionalCustom headers sent with outbound music webhook POSTs
webhook_secretstringOptionalSame request field shape as render webhook config
idempotency_keystringOptionalPayload-level dedupe key
metadataobjectOptionalFlat key-value pairs (strings, numbers, booleans; max 10 keys, 500 chars each) echoed in webhook payloads

Request Example (cURL)

curl -X POST "https://api.reelforger.com/v1/music/generate" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "idempotency-key: music-gen-001" \
  -d '{
    "prompt": "Cinematic lo-fi beat with a slow emotional build and no vocals",
    "custom_mode": false,
    "instrumental": true,
    "model": "V5",
    "metadata": {
      "make_scenario_id": "12345",
      "campaign": "spring_launch"
    },
    "webhook_url": "https://api.example.com/reelforger/webhooks",
    "webhook_headers": {
      "X-Environment": "production"
    }
  }'

Response Example (202 Accepted)

{
  "success": true,
  "data": {
    "job_id": "9e1d1a87-1c7c-4518-a7d6-b65e24f9e6af",
    "billing_policy": "flat_20_credits",
    "credit_cost": 20
  },
  "request_id": "req_abc123"
}

GET https://api.reelforger.com/v1/music/jobs/{jobId}

Poll a previously created music generation job. Once complete, the response includes tracks[] with both generated songs.

Parameters

NameTypeRequiredDescription
Authorization (header)stringRequiredBearer <API_KEY>
jobIdstring(uuid)RequiredMusic generation job ID

Request Example (cURL)

curl -X GET "https://api.reelforger.com/v1/music/jobs/9e1d1a87-1c7c-4518-a7d6-b65e24f9e6af" \
  -H "Authorization: Bearer <YOUR_API_KEY>"

Response Example (200 OK, completed)

{
  "success": true,
  "data": {
    "id": "9e1d1a87-1c7c-4518-a7d6-b65e24f9e6af",
    "status": "completed",
    "billing_policy": "flat_20_credits",
    "credit_cost": 20,
    "tracks": [
      {
        "id": "track-1",
        "audio_url": "https://assets.reelforger.com/music/night-drive-1.mp3",
        "title": "Night Drive Bloom 1",
        "duration": 87
      },
      {
        "id": "track-2",
        "audio_url": "https://assets.reelforger.com/music/night-drive-2.mp3",
        "title": "Night Drive Bloom 2",
        "duration": 91
      }
    ]
  },
  "request_id": "req_abc124"
}

End-to-end Example: Generate music, poll, then use a returned track in a render

  1. Create a music generation job with POST /v1/music/generate
  2. Poll GET /v1/music/jobs/{jobId} until status === "completed"
  3. Take one of the returned tracks[].audio_url values and use it as a standard audio asset in a later render

Render Request Example Using Returned audio_url

curl -X POST "https://api.reelforger.com/v1/videos/render" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -d '{
    "version": "v1",
    "output": { "width": 1080, "height": 1920, "fps": 30 },
    "assets": [
      { "id": "clip1", "type": "video", "url": "https://example.com/clip1.mp4" },
      { "id": "music1", "type": "audio", "url": "https://assets.reelforger.com/music/night-drive-1.mp3" }
    ],
    "composition": {
      "timeline": [
        {
          "id": "video-1",
          "type": "video",
          "asset_id": "clip1",
          "time": { "start_seconds": 0, "duration_seconds": 8 }
        },
        {
          "id": "audio-1",
          "type": "audio",
          "asset_id": "music1",
          "time": { "start_seconds": 0, "duration_seconds": 8 },
          "media_settings": { "volume": 0.35, "loop": true }
        }
      ]
    }
  }'

That audio URL behaves exactly like any other public audio file in the manifest system.


POST https://api.reelforger.com/v1/videos/validate

Validates a render payload without queueing a job or charging credits. Use this as a preflight check before render (recipes or timeline).

Parameters

NameTypeRequiredDescription
Authorization (header)stringRequiredBearer <API_KEY>
versionstringRequiredManifest version
output.widthnumberRequiredOutput width in pixels
output.heightnumberRequiredOutput height in pixels
output.fpsnumberRequiredOutput FPS (30 for launch)
assets[]arrayOptionalAsset list
composition.timeline[]arrayOptionalMedia timeline layers
composition.text_overlays[]arrayOptionalText overlay layers
composition.captionsobjectOptionalProgrammatic captions block
composition.captions.wordsarrayOptionalIf supplied, ReelForger uses these words directly for caption timing
composition.captions.transcription_source_asset_idstringOptionalExplicit caption transcription source when words are omitted (required if source is ambiguous)
webhook_urlstring(url)OptionalAccepted but ignored during validate; included so you can reuse the exact same request body for /render
webhook_headersobjectOptionalAccepted but ignored during validate
webhook_secretstringOptionalAccepted but ignored during validate

Request Example (cURL)

curl -X POST "https://api.reelforger.com/v1/videos/validate" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -d '{
    "version": "v1",
    "output": { "width": 1080, "height": 1920, "fps": 30 },
    "assets": [
      { "id": "clip1", "type": "video", "url": "https://example.com/clip1.mp4" }
    ],
    "composition": {
      "timeline": [
        {
          "id": "video-1",
          "type": "video",
          "asset_id": "clip1",
          "time": { "start_seconds": 0, "duration_seconds": 6 }
        }
      ],
      "captions": {
        "provider": "assemblyai",
        "preset": "karaoke_yellow",
        "mode": "phrase_karaoke",
        "layout": { "y": "78%" },
        "words": [
          { "text": "The", "start": 0, "end": 200 },
          { "text": "striker", "start": 200, "end": 600 }
        ]
      }
    }
  }'

Response Example (200 OK)

{
  "success": true,
  "data": {
    "valid": true,
    "estimated_total_duration_seconds": 6,
    "estimated_credit_cost": 6,
    "warnings": [
      {
        "code": "captions_near_bottom",
        "message": "Caption y is near the bottom edge. Consider 70%-80% for safer readability across presets.",
        "path": "composition.captions.layout.y"
      }
    ]
  },
  "request_id": "req_abc123"
}

Common Warning Codes

CodeMeaningRecommended Action
duration_near_limitRender duration is close to the 300-second capTrim timeline duration or shorten late-starting layers to preserve margin below 300s.
captions_near_bottomCaption layout may clip near lower edgeMove captions up (layout.y around 70%-80%) or reduce style.font_size.
captions_without_correct_textCaptions may look less polished without punctuation alignmentProvide correct_text for punctuation/casing repair while preserving timestamps.
karaoke_no_custom_highlightKaraoke mode uses preset default highlight colorSet captions.style.highlight_color if brand color consistency is required.
small_visual_layer_default_backgroundSmall visual layer may stack unexpectedly without explicit z_index/transparent backgroundFor PiP/split-screen, set layout.z_index explicitly and use background_mode: "transparent" where needed.
low_vertical_positionVisual layer starts very close to canvas bottomRaise layout.y or reduce layer height to prevent clipping.

POST https://api.reelforger.com/v1/videos/render

Advanced custom Timeline API path. Submits a render manifest and enqueues an asynchronous video job.

Caption input behavior for timeline renders:

  • If composition.captions.words is supplied, ReelForger uses it directly.
  • If composition.captions.words is omitted, ReelForger may transcribe automatically.
  • If source selection is ambiguous, set composition.captions.transcription_source_asset_id.

Parameters

NameTypeRequiredDescription
Authorization (header)stringRequiredBearer <API_KEY>
idempotency-key (header)stringOptionalRequest dedupe key (header takes precedence)
versionstringRequiredManifest version
output.widthnumberRequiredOutput width in pixels (> 0)
output.heightnumberRequiredOutput height in pixels (> 0)
output.fpsnumberRequiredOutput FPS (> 0)
assets[]arrayOptionalAsset list (defaults to empty array)
assets[].idstringRequired (per asset)Unique asset ID
assets[].typeenumRequired (per asset)video | audio | image
assets[].urlstring(url)Required (per asset)Public URL for media
composition.auto_stitchbooleanOptionalDefaults to false
composition.duration_secondsnumberOptionalExplicit composition duration. Used first for image-duration inference when image time.duration_seconds is omitted.
composition.timeline[]arrayOptionalMedia layer list (video, audio, image only)
composition.timeline[].idstringRequiredUnique layer ID
composition.timeline[].typeenumRequiredvideo | audio | image
composition.timeline[].asset_idstringRequired for video/audio/imageMust reference assets[].id
composition.timeline[].timeobjectRequired for image (start_seconds required; duration_seconds may be inferred from composition duration). Required for video/audio unless composition.auto_stitch is true; untimed videos are stitched in order, while untimed audio in mixed timelines defaults to start_seconds: 0 and aligns to stitched video duration.Layer timing
composition.text_overlays[]arrayOptionalText overlay list (see Working with Text)
composition.timeline[].trim.start_secondsnumberOptionalTrim start (>= 0)
composition.timeline[].styleobjectOptionalCSS-like style overrides
composition.timeline[].media_settings.*objectOptionalLayer media controls (volume, fades, loop, etc.)
composition.captions.wordsarrayOptionalIf supplied, ReelForger uses these words directly for caption timing
composition.captions.transcription_source_asset_idstringOptionalExplicit caption transcription source when words are omitted (required if source is ambiguous)
webhook_urlstring(url)OptionalPublic HTTPS endpoint that accepts POST requests from ReelForger webhooks
webhook_headersobjectOptionalCustom headers sent with outbound webhook POST
webhook_secretstringOptionalOptional per-request signing override. If omitted, ReelForger uses your dashboard-managed whsec_... signing secret
idempotency_keystringOptionalPayload-level dedupe key
metadataobjectOptionalFlat key-value pairs (strings, numbers, booleans; max 10 keys, 500 chars each) echoed in webhook payload

Image duration precedence:

  • Explicit composition.timeline[].time.duration_seconds always wins.
  • If omitted for image, ReelForger uses composition.duration_seconds when provided.
  • Otherwise, ReelForger infers from the max timed end across timeline/text overlays.
  • If composition.auto_stitch is enabled with untimed media clips, ReelForger can infer duration at render-time from probed media lengths.

Practical pattern for talking-head + logo without precomputed duration:

  • Set composition.auto_stitch: true
  • Leave base video time omitted
  • Set logo image time.start_seconds only (omit image duration_seconds)

Request Example (cURL)

curl -X POST "https://api.reelforger.com/v1/videos/render" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -d '{
    "version": "v1",
    "output": { "width": 1080, "height": 1920, "fps": 30 },
    "assets": [
      { "id": "clip1", "type": "video", "url": "https://example.com/clip1.mp4" }
    ],
    "composition": {
      "auto_stitch": true,
      "timeline": [
        { "id": "video-1", "type": "video", "asset_id": "clip1" }
      ]
    },
    "webhook_url": "https://api.example.com/reelforger/webhooks",
    "webhook_headers": {
      "X-Environment": "production",
      "X-Integration": "render-pipeline"
    },
    "metadata": {
      "make_scenario_id": "12345",
      "user_email": "test@test.com"
    }
  }'

Request Example (Node.js fetch with webhook config)

const response = await fetch("https://api.reelforger.com/v1/videos/render", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <YOUR_API_KEY>",
  },
  body: JSON.stringify({
    version: "v1",
    output: { width: 1080, height: 1920, fps: 30 },
    assets: [
      { id: "clip1", type: "video", url: "https://example.com/clip1.mp4" }
    ],
    composition: {
      auto_stitch: true,
      timeline: [{ id: "video-1", type: "video", asset_id: "clip1" }]
    },
    webhook_url: "https://api.example.com/reelforger/webhooks",
    webhook_headers: {
      "X-Environment": "production"
    },
    metadata: { make_scenario_id: "12345", user_email: "test@test.com" }
    // webhook_secret is optional:
    // if omitted, ReelForger uses your dashboard-managed signing secret (whsec_...)
  })
});

const data = await response.json();
console.log(data);

Response Example (202 Accepted)

{
  "success": true,
  "job_id": "8f1fd0fe-63a5-4fef-8f2c-8f1225f6d309",
  "request_id": "req_abc123"
}

Response Example (200 OK, idempotent replay)

{
  "success": true,
  "data": {
    "deduped": true,
    "job": {
      "id": "8f1fd0fe-63a5-4fef-8f2c-8f1225f6d309",
      "status": "rendering",
      "output_url": null,
      "error_message": null,
      "created_at": "2026-02-26T10:00:00.000Z",
      "updated_at": "2026-02-26T10:00:05.000Z"
    }
  }
}

GET https://api.reelforger.com/v1/jobs/:jobId

Returns the current status for a previously submitted render job.

Status values include:

  • queued
  • transcribing
  • rendering
  • completed
  • transcription_failed
  • failed

Parameters

NameTypeRequiredDescription
Authorization (header)stringRequiredBearer <API_KEY>
jobId (path)stringRequiredJob identifier

Request Example (cURL)

curl -X GET "https://api.reelforger.com/v1/jobs/<JOB_ID>" \
  -H "Authorization: Bearer <YOUR_API_KEY>"

Response Example (200 OK)

{
  "success": true,
  "request_id": "req_abc123",
  "data": {
    "id": "8f1fd0fe-63a5-4fef-8f2c-8f1225f6d309",
    "status": "completed",
    "output_url": "https://assets.reelforger.com/user_xxx/job_yyy.mp4",
    "error_message": null,
    "created_at": "2026-02-26T10:00:00.000Z",
    "updated_at": "2026-02-26T10:00:45.000Z",
    "total_duration_seconds": 5.6,
    "credit_cost": 9,
    "billing": {
      "total_credit_cost": 9,
      "transcription_credit_cost": 3,
      "transcription_duration_seconds_billed": 14.6,
      "render_credit_cost": 6,
      "render_credit_refunded": false
    },
    "diagnostics": {
      "preflight_warning_count": 0,
      "preflight_warnings": [],
      "captions": {
        "enabled": true,
        "mode": "phrase_karaoke",
        "preset": "karaoke_yellow",
        "word_count": 15,
        "has_correct_text": true
      },
      "status_snapshot": "completed"
    }
  }
}
ReelForger