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

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

Recommended default for AI-agent workflows. Send a strict recipe request and ReelForge compiles it into a full manifest, enqueues the job, and returns a job_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
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"],
      "transcript_words": [{ "text": "Hello", "start": 0, "end": 900 }]
    }
  }'

Response Example (202 Accepted)

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

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
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 60-second capTrim timeline duration or shorten late-starting layers to preserve margin below 60s.
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.

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; then derived from clip order.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.)
webhook_urlstring(url)OptionalPublic HTTPS endpoint that accepts POST requests from Reel Forge webhooks
webhook_headersobjectOptionalCustom headers sent with outbound webhook POST
webhook_secretstringOptionalOptional per-request signing override. If omitted, Reel Forge 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, ReelForge uses composition.duration_seconds when provided.
  • Otherwise, ReelForge infers from the max timed end across timeline/text overlays.
  • If composition.auto_stitch is enabled with untimed media clips, ReelForge 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/reelforge/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/reelforge/webhooks",
    webhook_headers: {
      "X-Environment": "production"
    },
    metadata: { make_scenario_id: "12345", user_email: "test@test.com" }
    // webhook_secret is optional:
    // if omitted, Reel Forge 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": "processing",
      "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.

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": 6,
    "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"
    }
  }
}