API Reference
The PhantomFlow REST API uses JSON request and response bodies. All endpoints require authentication via the x-api-key header unless noted otherwise.
Base URL
https://api.phantomflow.devAuthentication
Pass your API key in the x-api-key header with every request. Keys prefixed with ve_live_ are production keys that consume credits. Keys prefixed with ve_test_ are sandbox keys that return mock responses without consuming credits.
curl https://api.phantomflow.dev/api/jobs \
-H "x-api-key: ve_live_your_key_here"Render
/api/renderSubmit a render job with a full movie script. Returns a job ID that you can poll for status. Credits are deducted based on the estimated render duration.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
width | integer | No | Video width in pixels. Default: 1920 |
height | integer | No | Video height in pixels. Default: 1080 |
fps | number | No | Frames per second. Default: 30 |
quality | string | No | Render quality: "low", "medium", or "high". Default: "high" |
scenes | array | Yes | Array of scene objects. Must contain at least one scene. |
webhook_uri | string | No | URL to receive a POST callback when the job completes or fails. |
exports | array | No | Export targets (S3, FTP, SFTP, email, YouTube). See Integrations docs. |
Example
curl -X POST https://api.phantomflow.dev/api/render \
-H "x-api-key: ve_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"width": 1920,
"height": 1080,
"quality": "high",
"scenes": [
{
"duration": 5,
"background": "#000000",
"elements": [
{
"type": "text",
"text": "Hello World",
"font_size": 48,
"color": "#FFFFFF",
"position": "center"
}
]
}
]
}'Response 202 Accepted
{
"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "queued",
"estimated_credits": 5,
"credits_remaining": 995
}Error Responses
400SCHEMA_VALIDATION_ERROR - Invalid request body401UNAUTHORIZED - Invalid or missing API key402INSUFFICIENT_CREDITS - Not enough credits429RATE_LIMIT_EXCEEDED / CONCURRENCY_LIMIT_EXCEEDED
/api/render/templateRender a video from a saved template. The template script has {{variable}} placeholders that are replaced with the provided values before rendering.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | Yes | The template ID (e.g., "tpl_abc123"). Must be owned by you or public. |
variables | object | No | Key-value pairs to substitute into the template script. |
Example
curl -X POST https://api.phantomflow.dev/api/render/template \
-H "x-api-key: ve_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"template_id": "tpl_abc123",
"variables": {
"headline": "Summer Sale",
"discount": "50% Off"
}
}'Response 202 Accepted
{
"job_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"status": "queued",
"estimated_credits": 10,
"credits_remaining": 985
}/api/render/schemaReturns the JSON Schema (draft-2020-12) for the render request body. No authentication required. Useful for validating your movie script before submitting.
curl https://api.phantomflow.dev/api/render/schemaJobs
/api/jobsList your render jobs with pagination and optional status filtering.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (1-based). Default: 1 |
limit | integer | No | Items per page (1-100). Default: 20 |
status | string | No | Filter by status: "queued", "processing", "completed", or "failed" |
Response 200 OK
{
"jobs": [
{
"job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "completed",
"progress": 1,
"stage": "complete",
"output_uri": "https://cdn.phantomflow.dev/output/a1b2c3d4.mp4",
"duration_seconds": 5.0,
"estimated_credits": 5,
"credits_used": 5,
"created_at": "2026-01-15T10:30:00Z",
"completed_at": "2026-01-15T10:30:12Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}/api/jobs/:job_idGet the current status of a specific render job. Poll this endpoint until status is completed or failed.
Response 200 OK
| Parameter | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | The job UUID |
status | string | Yes | "queued", "processing", "completed", or "failed" |
progress | number | Yes | Progress from 0 to 1 (1 = complete) |
stage | string | No | Current render stage (e.g., "rendering_scene_1", "stitching", "complete") |
output_uri | string | No | Download URL for the rendered video. Available when status is completed. |
duration_seconds | number | No | Total duration of the rendered video in seconds. |
estimated_credits | integer | Yes | Credits estimated before render. Deducted at submission. |
credits_used | integer | No | Actual credits consumed. May differ from estimate. Available on completion. |
error | object | No | Error details with code and message. Present when status is failed. |
created_at | string | Yes | ISO 8601 timestamp of job creation. |
completed_at | string | No | ISO 8601 timestamp of job completion. Null if not completed. |
curl https://api.phantomflow.dev/api/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "x-api-key: ve_live_your_key_here"/api/jobs/:job_id/progressSSEServer-Sent Events endpoint for real-time job progress streaming. Instead of polling, open a persistent connection and receive events as the render progresses.
Events
progressSent periodically with progress (0-1), stage name, and timestamp.
completeSent when the render finishes. Contains outputUri, creditsUsed, durationSeconds, and fileSizeBytes.
errorSent when the render fails. Contains error code and message.
Example
// Note: EventSource does not support custom headers.
// Pass the API key as a query parameter for SSE auth.
const source = new EventSource(
'https://api.phantomflow.dev/api/jobs/JOB_ID/progress?apiKey=ve_live_your_key_here'
);
source.addEventListener('progress', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.stage}: ${Math.round(data.progress * 100)}%`);
});
source.addEventListener('complete', (e) => {
const data = JSON.parse(e.data);
console.log(`Done: ${data.outputUri}`);
source.close();
});
source.addEventListener('error', (e) => {
const data = JSON.parse(e.data);
console.error(`Error: ${data.message}`);
source.close();
});Templates
/api/templatesCreate a new template from a movie script. Use {{variable_name}} placeholders in text fields for dynamic content.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Template name. |
script | object | Yes | Full movie script object (same schema as POST /api/render). |
is_public | boolean | No | Whether the template is accessible to other users. Default: false |
Response 201 Created
{
"id": "tpl_a1b2c3d4e5f6",
"name": "Product Demo",
"is_public": false,
"created_at": "2026-01-15T10:30:00Z"
}/api/templatesList all templates owned by the authenticated user.
{
"templates": [
{
"id": "tpl_a1b2c3d4e5f6",
"name": "Product Demo",
"is_public": false,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-01-16T08:00:00Z"
}
]
}/api/templates/:idRetrieve a specific template by ID. Returns the full script. You can access templates you own and public templates.
{
"id": "tpl_a1b2c3d4e5f6",
"name": "Product Demo",
"script": {
"width": 1920,
"height": 1080,
"scenes": [
{
"duration": 5,
"elements": [
{ "type": "text", "text": "{{headline}}", "font_size": 64 }
]
}
]
},
"is_public": false,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-01-16T08:00:00Z"
}/api/templates/:idUpdate a template. You can update the name, script, and/or public visibility. All fields are optional — only provided fields are updated.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | Updated template name. |
script | object | No | Updated movie script. |
is_public | boolean | No | Updated visibility. |
Response 200 OK
{
"id": "tpl_a1b2c3d4e5f6",
"name": "Updated Name",
"is_public": true,
"updated_at": "2026-01-17T09:00:00Z"
}/api/templates/:idDelete a template. Only the owner can delete a template. This action is permanent.
Response 200 OK
{ "deleted": true }