> ## 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 from Preview

> Render final video from an existing storyboard preview job output

## Overview

The Render from Preview API generates the final video file from an existing storyboard preview. This endpoint is part of the recommended two-step workflow: first create a preview to review and validate content, then render the final video once approved.

<Note>
  This endpoint requires a completed storyboard preview job ID. First use the [Create Storyboard Preview API](/api-reference/videos/create-storyboard-preview) to generate a preview, then use this endpoint to render the final video.
</Note>

<Tip>
  **Recommended Workflow:** Create Preview → Review Scenes → Make Adjustments (optional) → Render Final Video
</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**       | **This API**                                                                 | Render preview as-is without modifications         |
| **Render with Modifications** | [Render Video](/api-reference/videos/render-video)                           | 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           |

### Benefits of Two-Step Workflow

<CardGroup cols={2}>
  <Card title="Content Validation" icon="check-circle">
    Review scenes, visuals, and timing before committing to render
  </Card>

  <Card title="Cost Optimization" icon="coins">
    Only render videos that have been reviewed and approved
  </Card>

  <Card title="Iterative Refinement" icon="arrows-rotate">
    Make adjustments to the preview before final render
  </Card>

  <Card title="Quality Assurance" icon="shield-check">
    Ensure content meets requirements before production
  </Card>
</CardGroup>

***

## API Endpoint

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

***

## Request Parameters

### Path Parameters

<ParamField path="storyboardjobid" type="string" required>
  The job ID returned from a completed [Create Storyboard Preview](/api-reference/videos/create-storyboard-preview) request. This ID identifies the storyboard configuration to render.
</ParamField>

### 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 Parameters

The request body allows you to override or add settings from the original storyboard preview. All parameters are optional - if not provided, the original preview settings are used.

<ParamField body="webhook" type="string">
  URL where the completed video output will be sent via POST request when finished (max 500 characters). Overrides the webhook from the preview.
</ParamField>

***

## 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 and `STORYBOARD_JOB_ID` with the job ID from your preview request.
</Tip>

### Basic Render from Preview

<CodeGroup>
  ```bash cURL theme={null}
  curl --request PUT \
    --url https://api.pictory.ai/pictoryapis/v2/video/render/74c0cc68-e1ed-42f3-a527-8bfc0b46bdb9 \
    --header 'Authorization: YOUR_API_KEY' \
    --header 'Content-Type: application/json' \
    --data '{}' | python -m json.tool
  ```

  ```javascript JavaScript theme={null}
  const storyboardJobId = '74c0cc68-e1ed-42f3-a527-8bfc0b46bdb9';

  const response = await fetch(
    `https://api.pictory.ai/pictoryapis/v2/video/render/${storyboardJobId}`,
    {
      method: 'PUT',
      headers: {
        'Authorization': 'YOUR_API_KEY',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({})
    }
  );

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

  ```python Python theme={null}
  import requests

  storyboard_job_id = '74c0cc68-e1ed-42f3-a527-8bfc0b46bdb9'

  response = requests.put(
      f'https://api.pictory.ai/pictoryapis/v2/video/render/{storyboard_job_id}',
      headers={
          'Authorization': 'YOUR_API_KEY',
          'Content-Type': 'application/json'
      },
      json={}
  )

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

  ```php PHP theme={null}
  <?php

  $storyboardJobId = '74c0cc68-e1ed-42f3-a527-8bfc0b46bdb9';
  $url = "https://api.pictory.ai/pictoryapis/v2/video/render/{$storyboardJobId}";

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

  $response = curl_exec($ch);
  $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  if ($httpCode === 200) {
      $data = json_decode($response, true);
      echo 'Render Job ID: ' . $data['data']['jobId'] . PHP_EOL;
  } else {
      echo 'Request failed with status ' . $httpCode . PHP_EOL;
  }
  ?>
  ```

  ```go Go theme={null}
  package main

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

  func main() {
      storyboardJobId := "74c0cc68-e1ed-42f3-a527-8bfc0b46bdb9"
      url := fmt.Sprintf("https://api.pictory.ai/pictoryapis/v2/video/render/%s", storyboardJobId)

      requestBody, _ := json.Marshal(map[string]interface{}{})

      req, err := http.NewRequest("PUT", 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>

### Render with Webhook Notification

<CodeGroup>
  ```javascript JavaScript theme={null}
  const storyboardJobId = '74c0cc68-e1ed-42f3-a527-8bfc0b46bdb9';

  const response = await fetch(
    `https://api.pictory.ai/pictoryapis/v2/video/render/${storyboardJobId}`,
    {
      method: 'PUT',
      headers: {
        'Authorization': 'YOUR_API_KEY',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        webhook: 'https://your-server.com/video-complete'
      })
    }
  );

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

  ```python Python theme={null}
  import requests

  storyboard_job_id = '74c0cc68-e1ed-42f3-a527-8bfc0b46bdb9'

  response = requests.put(
      f'https://api.pictory.ai/pictoryapis/v2/video/render/{storyboard_job_id}',
      headers={
          'Authorization': 'YOUR_API_KEY',
          'Content-Type': 'application/json'
      },
      json={
          'webhook': 'https://your-server.com/video-complete'
      }
  )

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

  ```php PHP theme={null}
  <?php

  $storyboardJobId = '74c0cc68-e1ed-42f3-a527-8bfc0b46bdb9';
  $url = "https://api.pictory.ai/pictoryapis/v2/video/render/{$storyboardJobId}";

  $payload = [
      'webhook' => 'https://your-server.com/video-complete'
  ];

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

  $response = curl_exec($ch);
  $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  if ($httpCode === 200) {
      $data = json_decode($response, true);
      echo 'Render Job ID: ' . $data['data']['jobId'] . PHP_EOL;
  } else {
      echo 'Request failed with status ' . $httpCode . PHP_EOL;
  }
  ?>
  ```

  ```go Go theme={null}
  package main

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

  func main() {
      storyboardJobId := "74c0cc68-e1ed-42f3-a527-8bfc0b46bdb9"
      url := fmt.Sprintf("https://api.pictory.ai/pictoryapis/v2/video/render/%s", storyboardJobId)

      payload := map[string]interface{}{
          "webhook": "https://your-server.com/video-complete",
      }

      requestBody, _ := json.Marshal(payload)

      req, err := http.NewRequest("PUT", 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 Two-Step Workflow

<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 preview to complete
  async function waitForJob(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') {
        console.log('Preview ready!');
        return data.data;
      }

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

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

  // Step 3: Render final video from preview
  async function renderFromPreview(storyboardJobId) {
    const response = await fetch(`${API_BASE}/v2/video/render/${storyboardJobId}`, {
      method: 'PUT',
      headers: {
        'Authorization': API_KEY,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        webhook: 'https://your-server.com/video-complete'
      })
    });

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

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

    // Wait for preview
    await waitForJob(previewJobId);

    // Render final video
    const renderJobId = await renderFromPreview(previewJobId);
    console.log('Render Job ID:', renderJobId);

    // Wait for render
    const result = await waitForJob(renderJobId);
    console.log('Video URL:', result.videoURL);
  }

  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
              }]
          }
      )

      data = response.json()
      return data['data']['jobId']

  def wait_for_job(job_id):
      """Step 2: Wait for job to complete"""
      while True:
          response = requests.get(
              f'{API_BASE}/v1/jobs/{job_id}',
              headers={'Authorization': API_KEY}
          )

          data = response.json()
          status = data['data']['status']

          if status == 'completed':
              print('Job completed!')
              return data['data']

          if status == 'failed':
              raise Exception('Job failed')

          time.sleep(5)

  def render_from_preview(storyboard_job_id):
      """Step 3: Render final video from preview"""
      response = requests.put(
          f'{API_BASE}/v2/video/render/{storyboard_job_id}',
          headers={
              'Authorization': API_KEY,
              'Content-Type': 'application/json'
          },
          json={
              'webhook': 'https://your-server.com/video-complete'
          }
      )

      data = response.json()
      return data['data']['jobId']

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

      # Wait for preview
      wait_for_job(preview_job_id)

      # Render final video
      render_job_id = render_from_preview(preview_job_id)
      print(f'Render Job ID: {render_job_id}')

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

  if __name__ == '__main__':
      main()
  ```
</CodeGroup>

***

## Error Handling

<AccordionGroup>
  <Accordion title="400 - Invalid Storyboard Job ID">
    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INVALID_REQUEST",
        "message": "Invalid storyboard job ID format"
      }
    }
    ```

    **Solution:** Ensure you are using the correct job ID format (UUID) from a storyboard preview request.
  </Accordion>

  <Accordion title="404 - Storyboard Not Found">
    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "NOT_FOUND",
        "message": "Storyboard job not found"
      }
    }
    ```

    **Solution:** Verify the storyboard job ID exists and belongs to your account. The preview job must be completed before rendering.
  </Accordion>

  <Accordion title="400 - Preview Not Completed">
    ```json theme={null}
    {
      "success": false,
      "error": {
        "message": "Storyboard preview is not yet completed"
      }
    }
    ```

    **Solution:** Wait for the storyboard preview job to complete before calling this endpoint. Use the [Get Storyboard Preview Job by ID](/api-reference/jobs/get-storyboard-preview-job-by-id) API to check status.
  </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 before rendering (Step 1)
  </Card>

  <Card title="Render Storyboard Video" icon="video" href="/api-reference/videos/render-storyboard-video">
    Direct render without preview step
  </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="Get Projects" icon="folder" href="/api-reference/projects/get-projects">
    List saved video projects
  </Card>
</CardGroup>
