> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pictory.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Render Video with Modifications

> Render final video from modified storyboard elements returned in the preview response

## 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](/api-reference/videos/create-storyboard-preview) job, allowing you to make changes to scenes, visuals, text, and other elements before producing the final video.

<Note>
  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.
</Note>

<Tip>
  **Use this endpoint when you need to modify the preview** - change scenes, update visuals, adjust timing, or customize content before final render.
</Tip>

***

## Render Workflow Options

| Workflow                      | API                                                                          | When to Use                                        |
| ----------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------- |
| **Create Preview**            | [Create Storyboard Preview](/api-reference/videos/create-storyboard-preview) | Generate preview to review scenes before rendering |
| **Render from Preview**       | [Render from Preview](/api-reference/videos/render-from-preview)             | Render preview as-is without modifications         |
| **Render with Modifications** | **This API**                                                                 | Modify preview elements before rendering           |
| **Render Saved Project**      | [Render Project](/api-reference/videos/render-project)                       | Render existing project created in App or via API  |
| **Direct Render**             | [Render Storyboard Video](/api-reference/videos/render-storyboard-video)     | Skip preview, render directly from input           |

### When to Use This Endpoint

<CardGroup cols={2}>
  <Card title="Scene Modifications" icon="scissors">
    Add, remove, or reorder scenes from the preview
  </Card>

  <Card title="Visual Replacements" icon="image">
    Replace AI-selected backgrounds with custom visuals
  </Card>

  <Card title="Text Updates" icon="font">
    Modify subtitle text or scene content
  </Card>

  <Card title="Timing Adjustments" icon="clock">
    Change scene durations or transitions
  </Card>
</CardGroup>

***

## API Endpoint

```http theme={null}
POST https://api.pictory.ai/pictoryapis/v2/video/render
```

***

## Request Parameters

### Headers

<ParamField header="Authorization" type="string" required>
  API key for authentication

  ```
  Authorization: YOUR_API_KEY
  ```
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Must be `application/json`
</ParamField>

***

## 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-reference/jobs/get-storyboard-preview-job-by-id) API. The response includes a `renderParams` object containing the complete storyboard configuration:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

| Element                                          | Description                                                   |
| ------------------------------------------------ | ------------------------------------------------------------- |
| `output`                                         | Video output settings (width, height, format, name, title)    |
| `elements`                                       | Array of all video elements - audio, backgrounds, and text    |
| `elements[].url`                                 | Change background video/image URL for backgroundElement types |
| `elements[].text`                                | Update subtitle text for SceneText elements                   |
| `elements[].duration`                            | Adjust element timing                                         |
| `elements[].fontFamily`, `fontSize`, `fontColor` | Modify text styling                                           |
| `sceneMarkers`                                   | Scene timing and markers                                      |
| `subtitles`                                      | Subtitle text content                                         |

### Element Types

| elementType         | Description                          |
| ------------------- | ------------------------------------ |
| `audioElement`      | Voice-over or background music audio |
| `backgroundElement` | Scene background video or image      |
| `SceneText`         | Subtitle/caption text overlay        |

***

## Response

When the render request is successfully submitted, a job is created and a job ID is returned.

<ResponseField name="success" type="boolean">
  Indicates whether the request was successful
</ResponseField>

<ResponseField name="data" type="object">
  <ResponseField name="jobId" type="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](/api-reference/jobs/get-video-render-job-by-id) endpoint.
  </ResponseField>
</ResponseField>

<ResponseExample>
  ```json 200 - Job Created theme={null}
  {
      "success": true,
      "data": {
          "jobId": "265a7c1a-4985-4058-9208-68114f131a2b"
      }
  }
  ```

  ```json 401 - Unauthorized theme={null}
  {
      "message": "Unauthorized"
  }
  ```
</ResponseExample>

## Next Steps

Once you have the `jobId`, poll the [Get Video Render Job by ID](/api-reference/jobs/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

<Tip>
  Replace `YOUR_API_KEY` with your actual API key.
</Tip>

### Basic Render with Modified Elements

<CodeGroup>
  ```bash cURL theme={null}
  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
  ```

  ```javascript JavaScript theme={null}
  // 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);
  ```

  ```python Python theme={null}
  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 PHP theme={null}
  <?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;
  ?>
  ```

  ```go Go theme={null}
  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"])
  }
  ```
</CodeGroup>

### Complete Workflow: Preview, Modify, Render

<CodeGroup>
  ```javascript JavaScript theme={null}
  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();
  ```

  ```python Python theme={null}
  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()
  ```
</CodeGroup>

### Example: Replace Scene Background

<CodeGroup>
  ```javascript JavaScript theme={null}
  // 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)
  });
  ```

  ```python Python theme={null}
  # 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 PHP theme={null}
  <?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);
  ?>
  ```

  ```go Go theme={null}
  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)
  }
  ```
</CodeGroup>

***

## Error Handling

<AccordionGroup>
  <Accordion title="400 - Missing Required Fields">
    ```json theme={null}
    {
      "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.
  </Accordion>

  <Accordion title="400 - Invalid Request Structure">
    ```json theme={null}
    {
      "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.
  </Accordion>

  <Accordion title="400 - Invalid Element Configuration">
    ```json theme={null}
    {
      "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.
  </Accordion>

  <Accordion title="401 - Unauthorized">
    ```json theme={null}
    {
      "message": "Unauthorized"
    }
    ```

    **Solution:** Check your API key is valid and correctly formatted in the Authorization header.
  </Accordion>
</AccordionGroup>

***

## Related APIs

<CardGroup cols={2}>
  <Card title="Create Storyboard Preview" icon="eye" href="/api-reference/videos/create-storyboard-preview">
    Generate preview to get renderParams (Step 1)
  </Card>

  <Card title="Render from Preview" icon="film" href="/api-reference/videos/render-from-preview">
    Render preview as-is without modifications
  </Card>

  <Card title="Get Render Job Status" icon="spinner" href="/api-reference/jobs/get-video-render-job-by-id">
    Monitor rendering progress
  </Card>

  <Card title="Search Media" icon="images" href="/api-reference/media-management/search-media">
    Find replacement visuals for scenes
  </Card>
</CardGroup>
