Get Video Render Job by ID
curl --request GET \
--url https://api.pictory.ai/pictoryapis/v1/jobs/{jobid} \
--header 'Authorization: <authorization>'import requests
url = "https://api.pictory.ai/pictoryapis/v1/jobs/{jobid}"
headers = {"Authorization": "<authorization>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: '<authorization>'}};
fetch('https://api.pictory.ai/pictoryapis/v1/jobs/{jobid}', 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/v1/jobs/{jobid}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>"
],
]);
$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/v1/jobs/{jobid}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "<authorization>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.pictory.ai/pictoryapis/v1/jobs/{jobid}")
.header("Authorization", "<authorization>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pictory.ai/pictoryapis/v1/jobs/{jobid}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = '<authorization>'
response = http.request(request)
puts response.read_body{
"job_id": "265a7c1a-4985-4058-9208-68114f131a2b",
"success": true,
"data": {
"status": "in-progress"
}
}
{
"job_id": "265a7c1a-4985-4058-9208-68114f131a2b",
"success": true,
"data": {
"status": "in-progress",
"renderProgress": 2,
"renderProgressMessage": "Generating video",
"renderState": "RUNNING"
}
}
{
"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
}
}
{
"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."
}
}
{
"id": "265a7c1a-4985-4058-9208-68114f131a2b",
"success": false,
"data": {
"error_code": "5000",
"error_message": "JOB_NOT_FOUND"
}
}
{
"message": "Unauthorized"
}
Pictory Jobs
Get Video Render Job by ID
Retrieve the status and results of a video render job
GET
/
pictoryapis
/
v1
/
jobs
/
{jobid}
Get Video Render Job by ID
curl --request GET \
--url https://api.pictory.ai/pictoryapis/v1/jobs/{jobid} \
--header 'Authorization: <authorization>'import requests
url = "https://api.pictory.ai/pictoryapis/v1/jobs/{jobid}"
headers = {"Authorization": "<authorization>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: '<authorization>'}};
fetch('https://api.pictory.ai/pictoryapis/v1/jobs/{jobid}', 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/v1/jobs/{jobid}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>"
],
]);
$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/v1/jobs/{jobid}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "<authorization>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.pictory.ai/pictoryapis/v1/jobs/{jobid}")
.header("Authorization", "<authorization>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pictory.ai/pictoryapis/v1/jobs/{jobid}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = '<authorization>'
response = http.request(request)
puts response.read_body{
"job_id": "265a7c1a-4985-4058-9208-68114f131a2b",
"success": true,
"data": {
"status": "in-progress"
}
}
{
"job_id": "265a7c1a-4985-4058-9208-68114f131a2b",
"success": true,
"data": {
"status": "in-progress",
"renderProgress": 2,
"renderProgressMessage": "Generating video",
"renderState": "RUNNING"
}
}
{
"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
}
}
{
"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."
}
}
{
"id": "265a7c1a-4985-4058-9208-68114f131a2b",
"success": false,
"data": {
"error_code": "5000",
"error_message": "JOB_NOT_FOUND"
}
}
{
"message": "Unauthorized"
}
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.A valid API key is required to use this endpoint. Obtain your API key from the API Access page in your Pictory dashboard.
API Endpoint
GET https://api.pictory.ai/pictoryapis/v1/jobs/{jobid}
Request Parameters
Path Parameters
uuid
required
The unique identifier (UUID) of the video render job. This value is the
jobId returned by the Render from Preview, Render Video, Render Storyboard Video, or Render Project endpoints.Example: "265a7c1a-4985-4058-9208-68114f131a2b"Headers
string
required
API key for authentication (starts with
pictai_)Authorization: YOUR_API_KEY
Response
In-Progress Response
Returned while the video is being rendered. The response includes progress information once rendering has started:string
The unique identifier of the render job
boolean
true while the job is processingobject
string
"in-progress": the video is still being renderednumber
Rendering progress as a percentage (0–100). Present only after rendering has started.
string
Descriptive progress message (e.g.,
"Generating video"). Present only after rendering has started.string
Current render state (e.g.,
"RUNNING"). Present only after rendering has started.Failed Response
Returned when the render job has failed:string
The unique identifier of the render job
boolean
false when the job has failedobject
Completed Response
Returned when the render has finished successfully. The response contains all output URLs:string
The unique identifier of the render job
boolean
true when the job has completed successfullyobject
Contains the complete render output.
string
"completed": the video has been rendered successfullynumber
100: rendering is completestring
Direct download URL for the rendered video file (MP4)
string
Shareable URL for the rendered video
string
Embeddable URL for the rendered video, suitable for iframe integration
string
Direct download URL for the audio track (MP3)
string
URL for the video thumbnail image (JPG)
string
URL for the SRT subtitle file
string
URL for the plain text transcript file
string
URL for the WebVTT subtitle file
number
Total duration of the rendered video in seconds
number
Time taken to encode and render the video in seconds
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.Response Examples
{
"job_id": "265a7c1a-4985-4058-9208-68114f131a2b",
"success": true,
"data": {
"status": "in-progress"
}
}
{
"job_id": "265a7c1a-4985-4058-9208-68114f131a2b",
"success": true,
"data": {
"status": "in-progress",
"renderProgress": 2,
"renderProgressMessage": "Generating video",
"renderState": "RUNNING"
}
}
{
"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
}
}
{
"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."
}
}
{
"id": "265a7c1a-4985-4058-9208-68114f131a2b",
"success": false,
"data": {
"error_code": "5000",
"error_message": "JOB_NOT_FOUND"
}
}
{
"message": "Unauthorized"
}
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
Replace
YOUR_API_KEY with your actual API key and use the jobId returned from any render endpoint.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
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']}")
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
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";
}
?>
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)
}
}
}
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
Use a polling interval of 10–30 seconds when checking job status. Polling too frequently may result in rate limiting.
-
Use webhooks when possible. Configure a
webhookURL in the original render request to receive automatic notification when the job completes, rather than polling. -
Monitor renderProgress. Use the
renderProgressfield to display progress to users. This field provides a percentage value from 0 to 100. - Implement timeouts. Set a maximum wait time based on expected video length. Longer videos require more rendering time.
- Cache results. Once a render job completes, store the output URLs locally. Completed job data may be cleaned after a retention period.
-
Handle failures gracefully. Inspect
error_codeanderror_messagein failed responses to determine whether the issue is recoverable (for example, an invalid voice name can be corrected and the request resubmitted).
Was this page helpful?
