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

# Get Video Render Job by ID

> Retrieve the status and results of a video render job

## Overview

This endpoint retrieves the current status and results of a video render job using its unique job ID. While rendering is in progress, it returns the job status along with render progress percentage and state. Once the render completes, it returns the full set of output URLs for the rendered video, audio, subtitles, and thumbnail.

<Note>
  A valid API key is required to use this endpoint. Obtain your API key from the [API Access page](https://app.pictory.ai/api-access) in your Pictory dashboard.
</Note>

***

## API Endpoint

```http theme={null}
GET https://api.pictory.ai/pictoryapis/v1/jobs/{jobid}
```

***

## Request Parameters

### Path Parameters

<ParamField path="jobid" type="uuid" required>
  The unique identifier (UUID) of the video render job. This value is the `jobId` returned by the [Render from Preview](/api-reference/videos/render-from-preview), [Render Video](/api-reference/videos/render-video), [Render Storyboard Video](/api-reference/videos/render-storyboard-video), or [Render Project](/api-reference/videos/render-project) endpoints.

  **Example:** `"265a7c1a-4985-4058-9208-68114f131a2b"`
</ParamField>

### Headers

<ParamField header="Authorization" type="string" required>
  API key for authentication (starts with `pictai_`)

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

***

## Response

### In-Progress Response

Returned while the video is being rendered. The response includes progress information once rendering has started:

<ResponseField name="job_id" type="string">
  The unique identifier of the render job
</ResponseField>

<ResponseField name="success" type="boolean">
  `true` while the job is processing
</ResponseField>

<ResponseField name="data" type="object">
  <ResponseField name="status" type="string">
    `"in-progress"`: the video is still being rendered
  </ResponseField>

  <ResponseField name="renderProgress" type="number">
    Rendering progress as a percentage (0–100). Present only after rendering has started.
  </ResponseField>

  <ResponseField name="renderProgressMessage" type="string">
    Descriptive progress message (e.g., `"Generating video"`). Present only after rendering has started.
  </ResponseField>

  <ResponseField name="renderState" type="string">
    Current render state (e.g., `"RUNNING"`). Present only after rendering has started.
  </ResponseField>
</ResponseField>

### Failed Response

Returned when the render job has failed:

<ResponseField name="job_id" type="string">
  The unique identifier of the render job
</ResponseField>

<ResponseField name="success" type="boolean">
  `false` when the job has failed
</ResponseField>

<ResponseField name="data" type="object">
  <ResponseField name="status" type="string">
    `"failed"`: the video render has failed
  </ResponseField>

  <ResponseField name="error_code" type="string">
    Error code identifying the failure type
  </ResponseField>

  <ResponseField name="error_message" type="string">
    Descriptive message explaining the cause of the failure
  </ResponseField>
</ResponseField>

### Completed Response

Returned when the render has finished successfully. The response contains all output URLs:

<ResponseField name="job_id" type="string">
  The unique identifier of the render job
</ResponseField>

<ResponseField name="success" type="boolean">
  `true` when the job has completed successfully
</ResponseField>

<ResponseField name="data" type="object">
  Contains the complete render output.

  <ResponseField name="status" type="string">
    `"completed"`: the video has been rendered successfully
  </ResponseField>

  <ResponseField name="progress" type="number">
    `100`: rendering is complete
  </ResponseField>

  <ResponseField name="videoURL" type="string">
    Direct download URL for the rendered video file (MP4)
  </ResponseField>

  <ResponseField name="videoShareURL" type="string">
    Shareable URL for the rendered video
  </ResponseField>

  <ResponseField name="videoEmbedURL" type="string">
    Embeddable URL for the rendered video, suitable for iframe integration
  </ResponseField>

  <ResponseField name="audioURL" type="string">
    Direct download URL for the audio track (MP3)
  </ResponseField>

  <ResponseField name="thumbnail" type="string">
    URL for the video thumbnail image (JPG)
  </ResponseField>

  <ResponseField name="srtFile" type="string">
    URL for the SRT subtitle file
  </ResponseField>

  <ResponseField name="txtFile" type="string">
    URL for the plain text transcript file
  </ResponseField>

  <ResponseField name="vttFile" type="string">
    URL for the WebVTT subtitle file
  </ResponseField>

  <ResponseField name="videoDuration" type="number">
    Total duration of the rendered video in seconds
  </ResponseField>

  <ResponseField name="encodingDuration" type="number">
    Time taken to encode and render the video in seconds
  </ResponseField>

  <ResponseField name="aiCreditsUsed" type="number">
    Total AI credits consumed for generating AI visuals (images and video clips) during rendering. Present only when the video includes scenes with `aiVisual` configuration. The value is the sum of credits used across all AI-generated scenes.
  </ResponseField>
</ResponseField>

### Response Examples

<ResponseExample>
  ```json 200 - In Progress (Job Started) theme={null}
  {
      "job_id": "265a7c1a-4985-4058-9208-68114f131a2b",
      "success": true,
      "data": {
          "status": "in-progress"
      }
  }
  ```

  ```json 200 - In Progress (Rendering) theme={null}
  {
      "job_id": "265a7c1a-4985-4058-9208-68114f131a2b",
      "success": true,
      "data": {
          "status": "in-progress",
          "renderProgress": 2,
          "renderProgressMessage": "Generating video",
          "renderState": "RUNNING"
      }
  }
  ```

  ```json 200 - Completed theme={null}
  {
      "job_id": "265a7c1a-4985-4058-9208-68114f131a2b",
      "success": true,
      "data": {
          "status": "completed",
          "progress": 100,
          "videoURL": "https://d3uryq9bhgb5qr.cloudfront.net/.../demo_text_to_video3.mp4",
          "videoShareURL": "https://video.pictory.ai/.../20260326170050790Tkrsq44GvHibYw5",
          "videoEmbedURL": "https://video.pictory.ai/embed/.../20260326170050790Tkrsq44GvHibYw5",
          "audioURL": "https://d3uryq9bhgb5qr.cloudfront.net/.../demo_text_to_video3.mp3",
          "thumbnail": "https://d3uryq9bhgb5qr.cloudfront.net/.../265a7c1a-4985-4058-9208-68114f131a2b.jpg",
          "srtFile": "https://d3uryq9bhgb5qr.cloudfront.net/.../6392d927-1ccd-4ea0-a167-a6ad7b643132.srt",
          "txtFile": "https://d3uryq9bhgb5qr.cloudfront.net/.../438e574f-c9cb-4eed-82ea-8cf0b6629664.txt",
          "vttFile": "https://d3uryq9bhgb5qr.cloudfront.net/.../a6cc12f5-f016-481f-ab0c-44cba00e048e.vtt",
          "videoDuration": 65.6,
          "encodingDuration": 117,
          "aiCreditsUsed": 48
      }
  }
  ```

  ```json 200 - Failed theme={null}
  {
      "job_id": "265a7c1a-4985-4058-9208-68114f131a2b",
      "success": false,
      "data": {
          "status": "failed",
          "error_code": "TEXT_TO_VIDEO_FAILED",
          "error_message": "The AI voice speaker [Timm] is invalid and is not supported. Please provide valid AI voice speaker."
      }
  }
  ```

  ```json 200 - Invalid Job ID theme={null}
  {
      "id": "265a7c1a-4985-4058-9208-68114f131a2b",
      "success": false,
      "data": {
          "error_code": "5000",
          "error_message": "JOB_NOT_FOUND"
      }
  }
  ```

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

***

## Completed Response Fields

| Field              | Description                                                                                                                                              |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `videoURL`         | Direct download link for the rendered MP4 video file                                                                                                     |
| `videoShareURL`    | Shareable link to view the video on Pictory                                                                                                              |
| `videoEmbedURL`    | Embeddable URL suitable for iframe integration                                                                                                           |
| `audioURL`         | Direct download link for the extracted MP3 audio track                                                                                                   |
| `thumbnail`        | URL for the auto-generated video thumbnail (JPG)                                                                                                         |
| `srtFile`          | SRT subtitle file compatible with standard video players                                                                                                 |
| `txtFile`          | Plain text transcript file                                                                                                                               |
| `vttFile`          | WebVTT subtitle file for web-based video players                                                                                                         |
| `videoDuration`    | Total video length in seconds                                                                                                                            |
| `encodingDuration` | Total rendering time in seconds                                                                                                                          |
| `aiCreditsUsed`    | Total AI credits consumed for generating AI visuals (images and video clips). Present only when the video includes scenes with `aiVisual` configuration. |

***

## Code Examples

<Tip>
  Replace `YOUR_API_KEY` with your actual API key and use the `jobId` returned from any render endpoint.
</Tip>

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url 'https://api.pictory.ai/pictoryapis/v1/jobs/265a7c1a-4985-4058-9208-68114f131a2b' \
    --header 'Authorization: YOUR_API_KEY' \
    --header 'accept: application/json' | python -m json.tool
  ```

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

  def poll_render_job(api_key, job_id, max_wait=900, poll_interval=15):
      """
      Poll for video render job completion.

      Args:
          api_key: Your Pictory API key
          job_id: The render job ID
          max_wait: Maximum wait time in seconds (default 15 minutes)
          poll_interval: Polling interval in seconds (default 15 seconds)
      """
      url = f"https://api.pictory.ai/pictoryapis/v1/jobs/{job_id}"
      headers = {
          "Authorization": api_key,
          "accept": "application/json"
      }

      start_time = time.time()

      while time.time() - start_time < max_wait:
          response = requests.get(url, headers=headers)
          data = response.json()

          if not data.get("success"):
              error_data = data.get("data", {})
              print(f"Job failed: {error_data.get('error_message', 'Unknown error')}")
              return data

          status = data.get("data", {}).get("status")

          if status == "in-progress":
              progress = data["data"].get("renderProgress", 0)
              message = data["data"].get("renderProgressMessage", "Processing")
              print(f"Rendering... {progress}% - {message}")
              time.sleep(poll_interval)
              continue

          if status == "completed":
              result = data["data"]
              print(f"Render complete!")
              print(f"Video URL: {result.get('videoURL')}")
              print(f"Duration: {result.get('videoDuration')}s")
              print(f"Encoding time: {result.get('encodingDuration')}s")
              return data

          if status == "failed":
              error_msg = data["data"].get("error_message", "Unknown error")
              print(f"Render failed: {error_msg}")
              return data

          print(f"Unexpected status: {status}")
          return data

      print("Timeout waiting for render")
      return None

  # Usage
  result = poll_render_job("YOUR_API_KEY", "265a7c1a-4985-4058-9208-68114f131a2b")

  if result and result.get("success") and result["data"].get("status") == "completed":
      data = result["data"]
      print(f"\nVideo: {data['videoURL']}")
      print(f"Share: {data['videoShareURL']}")
      print(f"Thumbnail: {data['thumbnail']}")
      print(f"SRT: {data['srtFile']}")
  ```

  ```javascript JavaScript theme={null}
  const jobId = '265a7c1a-4985-4058-9208-68114f131a2b';

  async function pollRenderJob(apiKey, jobId, maxWait = 900000, pollInterval = 15000) {
    const url = `https://api.pictory.ai/pictoryapis/v1/jobs/${jobId}`;
    const startTime = Date.now();

    while (Date.now() - startTime < maxWait) {
      const response = await fetch(url, {
        method: 'GET',
        headers: {
          'Authorization': apiKey,
          'accept': 'application/json'
        }
      });

      const data = await response.json();

      if (!data.success) {
        console.log(`Job failed: ${data.data?.error_message || 'Unknown error'}`);
        return data;
      }

      const status = data.data?.status;

      if (status === 'in-progress') {
        const progress = data.data?.renderProgress || 0;
        const message = data.data?.renderProgressMessage || 'Processing';
        console.log(`Rendering... ${progress}% - ${message}`);
        await new Promise(resolve => setTimeout(resolve, pollInterval));
        continue;
      }

      if (status === 'completed') {
        console.log('Render complete!');
        console.log(`Video URL: ${data.data.videoURL}`);
        console.log(`Duration: ${data.data.videoDuration}s`);
        return data;
      }

      if (status === 'failed') {
        console.log(`Render failed: ${data.data?.error_message}`);
        return data;
      }

      console.log(`Unexpected status: ${status}`);
      return data;
    }

    console.log('Timeout waiting for render');
    return null;
  }

  // Usage
  const result = await pollRenderJob('YOUR_API_KEY', jobId);

  if (result?.success && result.data?.status === 'completed') {
    console.log(`\nVideo: ${result.data.videoURL}`);
    console.log(`Share: ${result.data.videoShareURL}`);
  }
  ```

  ```php PHP theme={null}
  <?php
  function pollRenderJob($apiKey, $jobId, $maxWait = 900, $pollInterval = 15) {
      $url = "https://api.pictory.ai/pictoryapis/v1/jobs/{$jobId}";
      $startTime = time();

      while (time() - $startTime < $maxWait) {
          $ch = curl_init($url);
          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
          curl_setopt($ch, CURLOPT_HTTPHEADER, [
              'Authorization: ' . $apiKey,
              'accept: application/json'
          ]);

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

          $data = json_decode($response, true);

          if (!$data['success']) {
              echo "Job failed: " . ($data['data']['error_message'] ?? 'Unknown error') . "\n";
              return $data;
          }

          $status = $data['data']['status'] ?? null;

          if ($status === 'in-progress') {
              $progress = $data['data']['renderProgress'] ?? 0;
              $message = $data['data']['renderProgressMessage'] ?? 'Processing';
              echo "Rendering... {$progress}% - {$message}\n";
              sleep($pollInterval);
              continue;
          }

          if ($status === 'completed') {
              echo "Render complete!\n";
              echo "Video URL: " . ($data['data']['videoURL'] ?? '') . "\n";
              echo "Duration: " . ($data['data']['videoDuration'] ?? 0) . "s\n";
              return $data;
          }

          if ($status === 'failed') {
              echo "Render failed: " . ($data['data']['error_message'] ?? 'Unknown error') . "\n";
              return $data;
          }

          echo "Unexpected status: {$status}\n";
          return $data;
      }

      echo "Timeout waiting for render\n";
      return null;
  }

  // Usage
  $result = pollRenderJob('YOUR_API_KEY', '265a7c1a-4985-4058-9208-68114f131a2b');

  if ($result && $result['success'] && ($result['data']['status'] ?? '') === 'completed') {
      echo "\nVideo: " . $result['data']['videoURL'] . "\n";
      echo "Share: " . $result['data']['videoShareURL'] . "\n";
  }
  ?>
  ```

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

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

  func pollRenderJob(apiKey, jobID string, maxWait, pollInterval time.Duration) (map[string]interface{}, error) {
      url := fmt.Sprintf("https://api.pictory.ai/pictoryapis/v1/jobs/%s", jobID)
      startTime := time.Now()

      for time.Since(startTime) < maxWait {
          req, _ := http.NewRequest("GET", url, nil)
          req.Header.Set("Authorization", apiKey)
          req.Header.Set("accept", "application/json")

          client := &http.Client{}
          resp, err := client.Do(req)
          if err != nil {
              return nil, err
          }

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

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

          success, _ := data["success"].(bool)
          if !success {
              fmt.Println("Job failed")
              return data, nil
          }

          jobData, _ := data["data"].(map[string]interface{})
          status, _ := jobData["status"].(string)

          if status == "in-progress" {
              progress, _ := jobData["renderProgress"].(float64)
              fmt.Printf("Rendering... %.0f%%\n", progress)
              time.Sleep(pollInterval)
              continue
          }

          if status == "completed" {
              fmt.Println("Render complete!")
              if videoURL, ok := jobData["videoURL"].(string); ok {
                  fmt.Printf("Video URL: %s\n", videoURL)
              }
              return data, nil
          }

          if status == "failed" {
              if msg, ok := jobData["error_message"].(string); ok {
                  fmt.Printf("Render failed: %s\n", msg)
              }
              return data, nil
          }

          fmt.Printf("Unexpected status: %s\n", status)
          return data, nil
      }

      return nil, fmt.Errorf("timeout waiting for render")
  }

  func main() {
      result, err := pollRenderJob(
          "YOUR_API_KEY",
          "265a7c1a-4985-4058-9208-68114f131a2b",
          15*time.Minute,
          15*time.Second,
      )
      if err != nil {
          fmt.Println("Error:", err)
          return
      }

      if success, ok := result["success"].(bool); ok && success {
          data := result["data"].(map[string]interface{})
          if videoURL, ok := data["videoURL"].(string); ok {
              fmt.Printf("\nVideo: %s\n", videoURL)
          }
      }
  }
  ```
</CodeGroup>

***

## Render Progress Stages

The render job progresses through the following stages:

| Stage          | `status`      | `renderState` | Description                                                           |
| -------------- | ------------- | ------------- | --------------------------------------------------------------------- |
| **Queued**     | `in-progress` | -             | Job is queued; pre-processing has not started                         |
| **Processing** | `in-progress` | `RUNNING`     | Pre-render processing (scene composition, audio mixing)               |
| **Rendering**  | `in-progress` | `RUNNING`     | Video frame rendering is in progress                                  |
| **Completed**  | `completed`   | -             | Video is ready for download                                           |
| **Failed**     | `failed`      | -             | Render has failed; check `error_code` and `error_message` for details |

***

## Polling Best Practices

<Warning>
  Use a polling interval of **10–30 seconds** when checking job status. Polling too frequently may result in rate limiting.
</Warning>

1. **Use webhooks when possible.** Configure a `webhook` URL in the original render request to receive automatic notification when the job completes, rather than polling.

2. **Monitor renderProgress.** Use the `renderProgress` field to display progress to users. This field provides a percentage value from 0 to 100.

3. **Implement timeouts.** Set a maximum wait time based on expected video length. Longer videos require more rendering time.

4. **Cache results.** Once a render job completes, store the output URLs locally. Completed job data may be cleaned after a retention period.

5. **Handle failures gracefully.** Inspect `error_code` and `error_message` in failed responses to determine whether the issue is recoverable (for example, an invalid voice name can be corrected and the request resubmitted).
