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.dev

Authentication

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

POST/api/render

Submit 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

ParameterTypeRequiredDescription
widthintegerNoVideo width in pixels. Default: 1920
heightintegerNoVideo height in pixels. Default: 1080
fpsnumberNoFrames per second. Default: 30
qualitystringNoRender quality: "low", "medium", or "high". Default: "high"
scenesarrayYesArray of scene objects. Must contain at least one scene.
webhook_uristringNoURL to receive a POST callback when the job completes or fails.
exportsarrayNoExport 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

  • 400 SCHEMA_VALIDATION_ERROR - Invalid request body
  • 401 UNAUTHORIZED - Invalid or missing API key
  • 402 INSUFFICIENT_CREDITS - Not enough credits
  • 429 RATE_LIMIT_EXCEEDED / CONCURRENCY_LIMIT_EXCEEDED
POST/api/render/template

Render a video from a saved template. The template script has {{variable}} placeholders that are replaced with the provided values before rendering.

Request Body

ParameterTypeRequiredDescription
template_idstringYesThe template ID (e.g., "tpl_abc123"). Must be owned by you or public.
variablesobjectNoKey-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
}
GET/api/render/schema

Returns 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/schema

Jobs

GET/api/jobs

List your render jobs with pagination and optional status filtering.

Query Parameters

ParameterTypeRequiredDescription
pageintegerNoPage number (1-based). Default: 1
limitintegerNoItems per page (1-100). Default: 20
statusstringNoFilter 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
}
GET/api/jobs/:job_id

Get the current status of a specific render job. Poll this endpoint until status is completed or failed.

Response 200 OK

ParameterTypeRequiredDescription
job_idstringYesThe job UUID
statusstringYes"queued", "processing", "completed", or "failed"
progressnumberYesProgress from 0 to 1 (1 = complete)
stagestringNoCurrent render stage (e.g., "rendering_scene_1", "stitching", "complete")
output_uristringNoDownload URL for the rendered video. Available when status is completed.
duration_secondsnumberNoTotal duration of the rendered video in seconds.
estimated_creditsintegerYesCredits estimated before render. Deducted at submission.
credits_usedintegerNoActual credits consumed. May differ from estimate. Available on completion.
errorobjectNoError details with code and message. Present when status is failed.
created_atstringYesISO 8601 timestamp of job creation.
completed_atstringNoISO 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"
GET/api/jobs/:job_id/progressSSE

Server-Sent Events endpoint for real-time job progress streaming. Instead of polling, open a persistent connection and receive events as the render progresses.

Events

progress

Sent periodically with progress (0-1), stage name, and timestamp.

complete

Sent when the render finishes. Contains outputUri, creditsUsed, durationSeconds, and fileSizeBytes.

error

Sent 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

POST/api/templates

Create a new template from a movie script. Use {{variable_name}} placeholders in text fields for dynamic content.

Request Body

ParameterTypeRequiredDescription
namestringYesTemplate name.
scriptobjectYesFull movie script object (same schema as POST /api/render).
is_publicbooleanNoWhether 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"
}
GET/api/templates

List 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"
    }
  ]
}
GET/api/templates/:id

Retrieve 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"
}
PUT/api/templates/:id

Update a template. You can update the name, script, and/or public visibility. All fields are optional — only provided fields are updated.

Request Body

ParameterTypeRequiredDescription
namestringNoUpdated template name.
scriptobjectNoUpdated movie script.
is_publicbooleanNoUpdated visibility.

Response 200 OK

{
  "id": "tpl_a1b2c3d4e5f6",
  "name": "Updated Name",
  "is_public": true,
  "updated_at": "2026-01-17T09:00:00Z"
}
DELETE/api/templates/:id

Delete a template. Only the owner can delete a template. This action is permanent.

Response 200 OK

{ "deleted": true }