Skip to main content
POST
/
pictoryapis
/
v2
/
video
/
render
Render Video with Modifications
curl --request POST \
  --url https://api.pictory.ai/pictoryapis/v2/video/render \
  --header 'Authorization: <authorization>' \
  --header 'Content-Type: <content-type>'
import requests

url = "https://api.pictory.ai/pictoryapis/v2/video/render"

headers = {
"Authorization": "<authorization>",
"Content-Type": "<content-type>"
}

response = requests.post(url, headers=headers)

print(response.text)
const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': '<content-type>'}
};

fetch('https://api.pictory.ai/pictoryapis/v2/video/render', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));
<?php

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pictory.ai/pictoryapis/v2/video/render",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: <content-type>"
],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
package main

import (
"fmt"
"net/http"
"io"
)

func main() {

url := "https://api.pictory.ai/pictoryapis/v2/video/render"

req, _ := http.NewRequest("POST", url, nil)

req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "<content-type>")

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := io.ReadAll(res.Body)

fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("https://api.pictory.ai/pictoryapis/v2/video/render")
.header("Authorization", "<authorization>")
.header("Content-Type", "<content-type>")
.asString();
require 'uri'
require 'net/http'

url = URI("https://api.pictory.ai/pictoryapis/v2/video/render")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = '<content-type>'

response = http.request(request)
puts response.read_body
{
    "success": true,
    "data": {
        "jobId": "265a7c1a-4985-4058-9208-68114f131a2b"
    }
}
{
    "message": "Unauthorized"
}

Overview

The Render Video API generates a final video from storyboard elements that can be modified before rendering. This endpoint uses the renderParams property returned from a completed Create Storyboard Preview job, allowing you to make changes to scenes, visuals, text, and other elements before producing the final video.
This endpoint requires the renderParams object from a completed preview job. First create a preview, retrieve the job output to get renderParams, modify as needed, then submit to this endpoint.
Use this endpoint when you need to modify the preview - change scenes, update visuals, adjust timing, or customize content before final render.

Render Workflow Options

WorkflowAPIWhen to Use
Create PreviewCreate Storyboard PreviewGenerate preview to review scenes before rendering
Render from PreviewRender from PreviewRender preview as-is without modifications
Render with ModificationsThis APIModify preview elements before rendering
Render Saved ProjectRender ProjectRender existing project created in App or via API
Direct RenderRender Storyboard VideoSkip preview, render directly from input

When to Use This Endpoint

Scene Modifications

Add, remove, or reorder scenes from the preview

Visual Replacements

Replace AI-selected backgrounds with custom visuals

Text Updates

Modify subtitle text or scene content

Timing Adjustments

Change scene durations or transitions

API Endpoint

POST https://api.pictory.ai/pictoryapis/v2/video/render

Request Parameters

Headers

Authorization
string
required
API key for authentication
Authorization: YOUR_API_KEY
Content-Type
string
required
Must be application/json

Request Body

The request body should contain the values from the renderParams object returned in a completed preview job response. Send the renderParams content directly as the request body (not wrapped in a renderParams property). You can modify any properties before submitting.

Getting renderParams from Preview Job

After creating a storyboard preview, retrieve the job output using the Get Storyboard Preview Job by ID API. The response includes a renderParams object containing the complete storyboard configuration:
{
  "job_id": "2cc183fc-f07a-4bf1-b7a0-92ec999b22f3",
  "success": true,
  "data": {
    "status": "completed",
    "renderParams": {
      "output": {
        "width": 1920,
        "height": 1080,
        "format": "mp4",
        "name": "my_video.mp4",
        "title": "my_video",
        "description": ""
      },
      "elements": [
        {
          "type": "audio",
          "id": "voiceOver",
          "elementType": "audioElement",
          "url": "https://...",
          "segments": [...]
        },
        {
          "type": "audio",
          "id": "bgMusic",
          "elementType": "audioElement",
          "url": "https://...",
          "segments": [...]
        },
        {
          "id": "backgroundElement_1",
          "elementType": "backgroundElement",
          "type": "video",
          "url": "https://...",
          "visualUrl": "https://...",
          "startTime": 0,
          "duration": 10.53
        },
        {
          "id": "SceneText_1_1",
          "elementType": "SceneText",
          "type": "text",
          "text": "Your subtitle text here...",
          "fontFamily": "Arial",
          "fontSize": "24",
          "startTime": 0,
          "duration": 10.53
        }
      ],
      "sceneMarkers": [...],
      "subtitles": [...],
      "projectId": "1767054556293"
    },
    "previewUrl": "https://video.pictory.ai/v2/preview/...",
    "projectUrl": "https://app.pictory.ai/v2/storyboard/...",
    "projectId": "1767054556293"
  }
}
Use the contents of renderParams directly as the request body for this API:
{
  "output": {
    "width": 1920,
    "height": 1080,
    "format": "mp4",
    "name": "my_video.mp4",
    "title": "my_video"
  },
  "elements": [...],
  "sceneMarkers": [...],
  "subtitles": [...],
  "projectId": "1767054556293"
}

Modifiable Elements

Within renderParams, you can modify:
ElementDescription
outputVideo output settings (width, height, format, name, title)
elementsArray of all video elements - audio, backgrounds, and text
elements[].urlChange background video/image URL for backgroundElement types
elements[].textUpdate subtitle text for SceneText elements
elements[].durationAdjust element timing
elements[].fontFamily, fontSize, fontColorModify text styling
sceneMarkersScene timing and markers
subtitlesSubtitle text content

Element Types

elementTypeDescription
audioElementVoice-over or background music audio
backgroundElementScene background video or image
SceneTextSubtitle/caption text overlay

Response

When the render request is successfully submitted, a job is created and a job ID is returned.
success
boolean
Indicates whether the request was successful
data
object
jobId
string
Unique identifier for the render job. Use this to track the job status and retrieve results via the Get Video Render Job by ID endpoint.
{
    "success": true,
    "data": {
        "jobId": "265a7c1a-4985-4058-9208-68114f131a2b"
    }
}
{
    "message": "Unauthorized"
}

Next Steps

Once you have the jobId, poll the Get Video Render Job by ID endpoint to check the render status and retrieve the output URLs when complete. Use a polling interval of 10–30 seconds.

Code Examples

Replace YOUR_API_KEY with your actual API key.

Basic Render with Modified Elements

curl --request POST \
  --url https://api.pictory.ai/pictoryapis/v2/video/render \
  --header 'Authorization: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "output": {
      "width": 1920,
      "height": 1080,
      "format": "mp4",
      "name": "modified_video.mp4",
      "title": "modified_video"
    },
    "elements": [...],
    "sceneMarkers": [...],
    "subtitles": [...],
    "projectId": "1767054556293"
  }' | python -m json.tool
// Assume renderParams was retrieved from preview job response
const renderParams = previewJobResponse.data.renderParams;

// Modify as needed - change output title
renderParams.output.title = 'modified_video';
renderParams.output.name = 'modified_video.mp4';

// Send renderParams directly as the request body
const response = await fetch('https://api.pictory.ai/pictoryapis/v2/video/render', {
  method: 'POST',
  headers: {
    'Authorization': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(renderParams)
});

const data = await response.json();
console.log('Render Job ID:', data.data.jobId);
import requests

# Assume render_params was retrieved from preview job response
render_params = preview_job_response['data']['renderParams']

# Modify as needed - change output title
render_params['output']['title'] = 'modified_video'
render_params['output']['name'] = 'modified_video.mp4'

# Send render_params directly as the request body
response = requests.post(
    'https://api.pictory.ai/pictoryapis/v2/video/render',
    headers={
        'Authorization': 'YOUR_API_KEY',
        'Content-Type': 'application/json'
    },
    json=render_params
)

data = response.json()
print('Render Job ID:', data['data']['jobId'])
<?php

// Assume $renderParams was retrieved from preview job response
$renderParams = $previewJobResponse['data']['renderParams'];

// Modify as needed - change output title
$renderParams['output']['title'] = 'modified_video';
$renderParams['output']['name'] = 'modified_video.mp4';

// Send renderParams directly as the request body
$url = 'https://api.pictory.ai/pictoryapis/v2/video/render';

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: YOUR_API_KEY',
    'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($renderParams));

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
echo 'Render Job ID: ' . $data['data']['jobId'] . PHP_EOL;
?>
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

func main() {
    // Assume renderParams was retrieved from preview job response
    var renderParams map[string]interface{}
    // renderParams = previewJobResponse["data"].(map[string]interface{})["renderParams"]

    // Modify as needed - change output title
    output := renderParams["output"].(map[string]interface{})
    output["title"] = "modified_video"
    output["name"] = "modified_video.mp4"

    // Send renderParams directly as the request body
    url := "https://api.pictory.ai/pictoryapis/v2/video/render"

    requestBody, _ := json.Marshal(renderParams)

    req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
    if err != nil {
        panic(err)
    }

    req.Header.Set("Authorization", "YOUR_API_KEY")
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)

    var result map[string]interface{}
    json.Unmarshal(body, &result)

    data := result["data"].(map[string]interface{})
    fmt.Println("Render Job ID:", data["jobId"])
}

Complete Workflow: Preview, Modify, Render

const API_KEY = 'YOUR_API_KEY';
const API_BASE = 'https://api.pictory.ai/pictoryapis';

// Step 1: Create storyboard preview
async function createPreview() {
  const response = await fetch(`${API_BASE}/v2/video/storyboard`, {
    method: 'POST',
    headers: {
      'Authorization': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      videoName: 'my_video',
      voiceOver: {
        enabled: true,
        aiVoices: [{ speaker: 'Brian', speed: 100 }]
      },
      scenes: [{
        story: 'Welcome to our product demo. We will show you amazing features.',
        createSceneOnEndOfSentence: true
      }]
    })
  });

  const data = await response.json();
  return data.data.jobId;
}

// Step 2: Wait for job and get renderParams
async function getJobResult(jobId) {
  while (true) {
    const response = await fetch(`${API_BASE}/v1/jobs/${jobId}`, {
      headers: { 'Authorization': API_KEY }
    });

    const data = await response.json();

    if (data.data.status === 'completed') {
      return data.data;
    }

    if (data.data.status === 'failed') {
      throw new Error('Job failed');
    }

    await new Promise(resolve => setTimeout(resolve, 5000));
  }
}

// Step 3: Modify renderParams and render
async function renderWithModifications(renderParams) {
  // Example modifications:

  // Change output title
  renderParams.output.title = 'modified_video';
  renderParams.output.name = 'modified_video.mp4';

  // Modify a background element's URL (find backgroundElement by id)
  // const bgElement = renderParams.elements.find(el => el.id === 'backgroundElement_1');
  // if (bgElement) bgElement.url = 'https://example.com/new-bg.mp4';

  // Submit renderParams directly as the request body
  const response = await fetch(`${API_BASE}/v2/video/render`, {
    method: 'POST',
    headers: {
      'Authorization': API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(renderParams)
  });

  const data = await response.json();
  return data.data.jobId;
}

// Execute workflow
async function main() {
  try {
    // Create preview
    console.log('Creating preview...');
    const previewJobId = await createPreview();
    console.log('Preview Job ID:', previewJobId);

    // Wait for preview and get renderParams
    console.log('Waiting for preview...');
    const previewResult = await getJobResult(previewJobId);
    console.log('Preview completed!');

    const renderParams = previewResult.renderParams;

    // Modify and render
    console.log('Rendering with modifications...');
    const renderJobId = await renderWithModifications(renderParams);
    console.log('Render Job ID:', renderJobId);

    // Wait for render
    const renderResult = await getJobResult(renderJobId);
    console.log('Video URL:', renderResult.videoURL);
  } catch (error) {
    console.error('Error:', error.message);
  }
}

main();
import requests
import time

API_KEY = 'YOUR_API_KEY'
API_BASE = 'https://api.pictory.ai/pictoryapis'

def create_preview():
    """Step 1: Create storyboard preview"""
    response = requests.post(
        f'{API_BASE}/v2/video/storyboard',
        headers={
            'Authorization': API_KEY,
            'Content-Type': 'application/json'
        },
        json={
            'videoName': 'my_video',
            'voiceOver': {
                'enabled': True,
                'aiVoices': [{'speaker': 'Brian', 'speed': 100}]
            },
            'scenes': [{
                'story': 'Welcome to our product demo. We will show you amazing features.',
                'createSceneOnEndOfSentence': True
            }]
        }
    )
    return response.json()['data']['jobId']

def get_job_result(job_id):
    """Step 2: Wait for job and get result"""
    while True:
        response = requests.get(
            f'{API_BASE}/v1/jobs/{job_id}',
            headers={'Authorization': API_KEY}
        )
        data = response.json()

        if data['data']['status'] == 'completed':
            return data['data']

        if data['data']['status'] == 'failed':
            raise Exception('Job failed')

        time.sleep(5)

def render_with_modifications(render_params):
    """Step 3: Modify renderParams and render"""
    # Example modifications:

    # Change output title
    render_params['output']['title'] = 'modified_video'
    render_params['output']['name'] = 'modified_video.mp4'

    # Modify a background element's URL (find backgroundElement by id)
    # for el in render_params['elements']:
    #     if el.get('id') == 'backgroundElement_1':
    #         el['url'] = 'https://example.com/new-bg.mp4'

    # Submit render_params directly as the request body
    response = requests.post(
        f'{API_BASE}/v2/video/render',
        headers={
            'Authorization': API_KEY,
            'Content-Type': 'application/json'
        },
        json=render_params
    )
    return response.json()['data']['jobId']

def main():
    try:
        # Create preview
        print('Creating preview...')
        preview_job_id = create_preview()
        print(f'Preview Job ID: {preview_job_id}')

        # Wait for preview and get renderParams
        print('Waiting for preview...')
        preview_result = get_job_result(preview_job_id)
        print('Preview completed!')

        render_params = preview_result['renderParams']

        # Modify and render
        print('Rendering with modifications...')
        render_job_id = render_with_modifications(render_params)
        print(f'Render Job ID: {render_job_id}')

        # Wait for render
        render_result = get_job_result(render_job_id)
        print(f'Video URL: {render_result["videoURL"]}')

    except Exception as e:
        print(f'Error: {e}')

if __name__ == '__main__':
    main()

Example: Replace Scene Background

// Get renderParams from preview job
const renderParams = previewJobResponse.data.renderParams;

// Find and replace background for first scene (backgroundElement_1)
const bgElement = renderParams.elements.find(el => el.id === 'backgroundElement_1');
if (bgElement) {
  bgElement.url = 'https://your-domain.com/custom-background.mp4';
  bgElement.visualUrl = 'https://your-domain.com/custom-background.mp4';
}

// Render with new background - send renderParams directly
const response = await fetch('https://api.pictory.ai/pictoryapis/v2/video/render', {
  method: 'POST',
  headers: {
    'Authorization': 'YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(renderParams)
});
# Get render_params from preview job
render_params = preview_job_response['data']['renderParams']

# Find and replace background for first scene (backgroundElement_1)
for element in render_params['elements']:
    if element.get('id') == 'backgroundElement_1':
        element['url'] = 'https://your-domain.com/custom-background.mp4'
        element['visualUrl'] = 'https://your-domain.com/custom-background.mp4'
        break

# Render with new background - send render_params directly
response = requests.post(
    'https://api.pictory.ai/pictoryapis/v2/video/render',
    headers={
        'Authorization': 'YOUR_API_KEY',
        'Content-Type': 'application/json'
    },
    json=render_params
)
<?php

// Get renderParams from preview job
$renderParams = $previewJobResponse['data']['renderParams'];

// Find and replace background for first scene (backgroundElement_1)
foreach ($renderParams['elements'] as &$element) {
    if ($element['id'] === 'backgroundElement_1') {
        $element['url'] = 'https://your-domain.com/custom-background.mp4';
        $element['visualUrl'] = 'https://your-domain.com/custom-background.mp4';
        break;
    }
}

// Render with new background - send renderParams directly
$url = 'https://api.pictory.ai/pictoryapis/v2/video/render';

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: YOUR_API_KEY',
    'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($renderParams));

$response = curl_exec($ch);
curl_close($ch);
?>
package main

import (
    "bytes"
    "encoding/json"
    "net/http"
)

func main() {
    // Get renderParams from preview job
    var renderParams map[string]interface{}
    // renderParams = previewJobResponse["data"].(map[string]interface{})["renderParams"]

    // Find and replace background for first scene (backgroundElement_1)
    elements := renderParams["elements"].([]interface{})
    for _, el := range elements {
        element := el.(map[string]interface{})
        if element["id"] == "backgroundElement_1" {
            element["url"] = "https://your-domain.com/custom-background.mp4"
            element["visualUrl"] = "https://your-domain.com/custom-background.mp4"
            break
        }
    }

    // Render with new background - send renderParams directly
    url := "https://api.pictory.ai/pictoryapis/v2/video/render"

    requestBody, _ := json.Marshal(renderParams)

    req, _ := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
    req.Header.Set("Authorization", "YOUR_API_KEY")
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    client.Do(req)
}

Error Handling

{
  "success": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "elements is required"
  }
}
Solution: Ensure your request body contains all required fields from the preview job’s renderParams including output, elements, sceneMarkers, and projectId. Send the renderParams content directly as the request body.
{
  "success": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "Invalid request structure"
  }
}
Solution: Ensure the request body maintains the required structure from the preview job’s renderParams. Only modify specific properties, do not remove required fields.
{
  "success": false,
  "error": {
    "message": "Invalid element configuration at index 0"
  }
}
Solution: Check that modified elements have valid configurations. Ensure URLs are accessible and properly formatted. Each element must have valid id, elementType, and type properties.
{
  "message": "Unauthorized"
}
Solution: Check your API key is valid and correctly formatted in the Authorization header.

Create Storyboard Preview

Generate preview to get renderParams (Step 1)

Render from Preview

Render preview as-is without modifications

Get Render Job Status

Monitor rendering progress

Search Media

Find replacement visuals for scenes