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
| Name | Type | Required | Description |
|---|---|---|---|
Authorization (header) | string | Required | Bearer <API_KEY> |
idempotency-key (header) | string | Optional | Request dedupe key (header takes precedence) |
recipe_id | enum | Required (canonical) | Recipe family identifier (for example voiceover_explainer) |
style_preset | enum | Required | Caption/style preset for generated overlays and captions |
variables | object | Required | Strict per-recipe variables object |
webhook_url | string(url) | Optional | Public endpoint that accepts webhook POST events |
webhook_headers | object | Optional | Optional custom webhook headers |
webhook_secret | string | Optional | Optional per-request webhook signing override |
idempotency_key | string | Optional | Payload-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
| Name | Type | Required | Description |
|---|---|---|---|
Authorization (header) | string | Required | Bearer <API_KEY> |
version | string | Required | Manifest version |
output.width | number | Required | Output width in pixels |
output.height | number | Required | Output height in pixels |
output.fps | number | Required | Output FPS (30 for launch) |
assets[] | array | Optional | Asset list |
composition.timeline[] | array | Optional | Media timeline layers |
composition.text_overlays[] | array | Optional | Text overlay layers |
composition.captions | object | Optional | Programmatic captions block |
webhook_url | string(url) | Optional | Accepted but ignored during validate; included so you can reuse the exact same request body for /render |
webhook_headers | object | Optional | Accepted but ignored during validate |
webhook_secret | string | Optional | Accepted 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
| Code | Meaning | Recommended Action |
|---|---|---|
duration_near_limit | Render duration is close to the 60-second cap | Trim timeline duration or shorten late-starting layers to preserve margin below 60s. |
captions_near_bottom | Caption layout may clip near lower edge | Move captions up (layout.y around 70%-80%) or reduce style.font_size. |
captions_without_correct_text | Captions may look less polished without punctuation alignment | Provide correct_text for punctuation/casing repair while preserving timestamps. |
karaoke_no_custom_highlight | Karaoke mode uses preset default highlight color | Set captions.style.highlight_color if brand color consistency is required. |
small_visual_layer_default_background | Small visual layer may stack unexpectedly without explicit z_index/transparent background | For PiP/split-screen, set layout.z_index explicitly and use background_mode: "transparent" where needed. |
low_vertical_position | Visual layer starts very close to canvas bottom | Raise 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
| Name | Type | Required | Description |
|---|---|---|---|
Authorization (header) | string | Required | Bearer <API_KEY> |
idempotency-key (header) | string | Optional | Request dedupe key (header takes precedence) |
version | string | Required | Manifest version |
output.width | number | Required | Output width in pixels (> 0) |
output.height | number | Required | Output height in pixels (> 0) |
output.fps | number | Required | Output FPS (> 0) |
assets[] | array | Optional | Asset list (defaults to empty array) |
assets[].id | string | Required (per asset) | Unique asset ID |
assets[].type | enum | Required (per asset) | video | audio | image |
assets[].url | string(url) | Required (per asset) | Public URL for media |
composition.auto_stitch | boolean | Optional | Defaults to false |
composition.duration_seconds | number | Optional | Explicit composition duration. Used first for image-duration inference when image time.duration_seconds is omitted. |
composition.timeline[] | array | Optional | Media layer list (video, audio, image only) |
composition.timeline[].id | string | Required | Unique layer ID |
composition.timeline[].type | enum | Required | video | audio | image |
composition.timeline[].asset_id | string | Required for video/audio/image | Must reference assets[].id |
composition.timeline[].time | object | Required 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[] | array | Optional | Text overlay list (see Working with Text) |
composition.timeline[].trim.start_seconds | number | Optional | Trim start (>= 0) |
composition.timeline[].style | object | Optional | CSS-like style overrides |
composition.timeline[].media_settings.* | object | Optional | Layer media controls (volume, fades, loop, etc.) |
webhook_url | string(url) | Optional | Public HTTPS endpoint that accepts POST requests from Reel Forge webhooks |
webhook_headers | object | Optional | Custom headers sent with outbound webhook POST |
webhook_secret | string | Optional | Optional per-request signing override. If omitted, Reel Forge uses your dashboard-managed whsec_... signing secret |
idempotency_key | string | Optional | Payload-level dedupe key |
metadata | object | Optional | Flat key-value pairs (strings, numbers, booleans; max 10 keys, 500 chars each) echoed in webhook payload |
Image duration precedence:
- Explicit
composition.timeline[].time.duration_secondsalways wins. - If omitted for
image, ReelForge usescomposition.duration_secondswhen provided. - Otherwise, ReelForge infers from the max timed end across timeline/text overlays.
- If
composition.auto_stitchis 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
timeomitted - Set logo image
time.start_secondsonly (omit imageduration_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
| Name | Type | Required | Description |
|---|---|---|---|
Authorization (header) | string | Required | Bearer <API_KEY> |
jobId (path) | string | Required | Job 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"
}
}
}