Get Job Status
curl --request GET \
--url https://api.tornadoapi.io/jobs/{id} \
--header 'x-api-key: <x-api-key>'import requests
url = "https://api.tornadoapi.io/jobs/{id}"
headers = {"x-api-key": "<x-api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<x-api-key>'}};
fetch('https://api.tornadoapi.io/jobs/{id}', 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.tornadoapi.io/jobs/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <x-api-key>"
],
]);
$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.tornadoapi.io/jobs/{id}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<x-api-key>")
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.tornadoapi.io/jobs/{id}")
.header("x-api-key", "<x-api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tornadoapi.io/jobs/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<x-api-key>'
response = http.request(request)
puts response.read_body{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
"status": "Completed",
"s3_url": "https://cdn.example.com/videos/video.mp4?X-Amz-Algorithm=...",
"subtitle_url": null,
"error": null,
"step": "Finished",
"title": "Rick Astley - Never Gonna Give You Up",
"description": "The official video for Never Gonna Give You Up...",
"release_date": "2009-10-25T00:00:00Z",
"download_speed_mbps": 45.2,
"upload_speed_mbps": 120.5,
"download_duration_ms": 3200,
"mux_duration_ms": 1500,
"upload_duration_ms": 2100,
"total_duration_ms": 8050,
"file_size": 52428800,
"native_video_codec": "avc1",
"native_audio_codec": "mp4a",
"download_strategy": "native",
"download_retries": 1,
"upload_retries": 0,
"queue_wait_ms": 450,
"requested_quality": "best",
"actual_quality": "1080p",
"created_at": 1705507200000,
"finished_at": 1705507260000
}
Jobs
Get Job Status
Get the status of a download job
GET
/
jobs
/
{id}
Get Job Status
curl --request GET \
--url https://api.tornadoapi.io/jobs/{id} \
--header 'x-api-key: <x-api-key>'import requests
url = "https://api.tornadoapi.io/jobs/{id}"
headers = {"x-api-key": "<x-api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<x-api-key>'}};
fetch('https://api.tornadoapi.io/jobs/{id}', 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.tornadoapi.io/jobs/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <x-api-key>"
],
]);
$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.tornadoapi.io/jobs/{id}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<x-api-key>")
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.tornadoapi.io/jobs/{id}")
.header("x-api-key", "<x-api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tornadoapi.io/jobs/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<x-api-key>'
response = http.request(request)
puts response.read_body{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
"status": "Completed",
"s3_url": "https://cdn.example.com/videos/video.mp4?X-Amz-Algorithm=...",
"subtitle_url": null,
"error": null,
"step": "Finished",
"title": "Rick Astley - Never Gonna Give You Up",
"description": "The official video for Never Gonna Give You Up...",
"release_date": "2009-10-25T00:00:00Z",
"download_speed_mbps": 45.2,
"upload_speed_mbps": 120.5,
"download_duration_ms": 3200,
"mux_duration_ms": 1500,
"upload_duration_ms": 2100,
"total_duration_ms": 8050,
"file_size": 52428800,
"native_video_codec": "avc1",
"native_audio_codec": "mp4a",
"download_strategy": "native",
"download_retries": 1,
"upload_retries": 0,
"queue_wait_ms": 450,
"requested_quality": "best",
"actual_quality": "1080p",
"created_at": 1705507200000,
"finished_at": 1705507260000
}
Overview
Retrieves the current status of a download job. When completed, includes a signed download URL for the file.Header Parameters
string
required
Your API key for authentication
Path Parameters
string
required
The job UUID returned from
POST /jobsResponse
string
Job UUID
string
Original source URL
string
Job status:
Pending, Processing, Completed, Failed, Warning, Skipped, Cancelled, or CancelledByAdminstring
Signed download URL (only when
Completed). Tornado-managed storage may use https://files.tornadoapi.io/<object-key>?verify=.... Your own S3 bucket retains its own endpoint and S3 signature. Use the returned URL unchanged when downloading, including its query string.The
files.tornadoapi.io delivery domain applies only to Tornado-managed storage. It does not replace a customer’s bucket endpoint or change their object key. On that domain, videos/ is part of the object key; there is no bucket-name segment in the path. A storage configuration that cannot be resolved does not fall back to a Tornado download URL.string
Presigned URL for subtitles (if available)
string
Presigned URL for the thumbnail (only when
download_thumbnail was enabled). Omitted otherwise.string
Error message (when
Failed, Warning, or Skipped)string
Error classification (when
Failed, Warning, or Skipped): error for technical failures (rate limits, bot detection, connection issues), warning for content issues (private video, members-only, geo-blocked, audio-only Spotify episodes)string
Current processing step:
Queued, Downloading, Muxing, Uploading, Finishedstring
Video title (only present for completed jobs fetched from database)
string
Video/episode description from the source platform (only present for completed jobs)
string
Release or upload date. Spotify: exact publish date. YouTube: upload date (midnight UTC). ISO 8601 format.
string
S3 folder prefix if provided at job creation
string | null
Requested video resolution preference, for example
144, 240, or lowest.string | null
Delivered video resolution, measured from the downloaded stream and expressed as its shorter dimension, for example
144p. It can differ from the request when that resolution is unavailable. This field is null for audio-only jobs and can be absent or null before completion or on older jobs.string
Batch UUID if this job is part of a batch (Spotify show or YouTube playlist)
number
Download speed in MB/s (only present for completed jobs)
number
Upload speed in MB/s (only present for completed jobs)
integer
Metadata extraction duration in milliseconds (only present for completed jobs)
integer
Download stage duration in milliseconds (only present for completed jobs)
integer
Mux (FFmpeg) stage duration in milliseconds (only present for completed jobs)
integer
Upload stage duration in milliseconds (only present for completed jobs)
integer
Total pipeline duration from pop to completion in milliseconds (only present for completed jobs)
integer
YouTube API pre-check duration in milliseconds (only present when pre-check was performed)
integer
Time spent waiting for IO semaphore in milliseconds (only present for completed jobs)
integer
Time spent waiting for CPU semaphore in milliseconds (only present for completed jobs)
integer
Time spent waiting for upload semaphore in milliseconds (only present for completed jobs)
integer
Subtitle download duration in milliseconds (only present for completed jobs with subtitles)
integer
File move duration in milliseconds (only present for completed jobs)
integer
File size in bytes (only present for completed jobs)
string
Video codec of the source stream (e.g.,
"avc1", "vp9", "av01"). Only present for completed jobs.string
Audio codec of the source stream (e.g.,
"mp4a", "opus"). Only present for completed jobs.string
Download strategy used:
native, ytdlp, cascade. Only present for completed jobs.integer
Total download attempts across all strategies (only present for completed jobs)
integer
Number of download retries attempted (only present for completed jobs)
integer
Number of upload retries attempted (only present for completed jobs)
integer
Time spent waiting in queue in milliseconds (only present for completed jobs)
string
Quality requested by the user (only present for completed jobs)
string
Actual quality of the downloaded video (only present for completed jobs)
string
Webhook delivery status (only present when webhook_url was set)
integer
Creation timestamp in milliseconds since epoch
integer
Completion timestamp in milliseconds since epoch (only present for completed jobs)
Examples
curl -X GET "https://api.tornadoapi.io/jobs/550e8400-e29b-41d4-a716-446655440000" \
-H "x-api-key: sk_your_api_key"
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
"status": "Pending",
"s3_url": null,
"subtitle_url": null,
"error": null,
"error_type": null,
"step": "Queued"
}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
"status": "Processing",
"s3_url": null,
"subtitle_url": null,
"error": null,
"error_type": null,
"step": "Downloading"
}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
"status": "Completed",
"s3_url": "https://cdn.example.com/videos/video.mp4?X-Amz-Algorithm=...",
"subtitle_url": null,
"error": null,
"step": "Finished",
"title": "Rick Astley - Never Gonna Give You Up",
"description": "The official video for Never Gonna Give You Up...",
"release_date": "2009-10-25T00:00:00Z",
"folder": "music-videos",
"download_speed_mbps": 45.2,
"upload_speed_mbps": 120.5,
"extract_duration_ms": 800,
"download_duration_ms": 3200,
"mux_duration_ms": 1500,
"upload_duration_ms": 2100,
"total_duration_ms": 8050,
"file_size": 52428800,
"native_video_codec": "avc1",
"native_audio_codec": "mp4a",
"download_strategy": "native",
"cascade_total_attempts": 1,
"download_retries": 1,
"upload_retries": 0,
"queue_wait_ms": 450,
"requested_quality": "best",
"actual_quality": "1080p",
"webhook_status": "delivered",
"created_at": 1705507200000,
"finished_at": 1705507260000
}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
"status": "Failed",
"s3_url": null,
"subtitle_url": null,
"error": "Video unavailable",
"error_type": "warning",
"step": "Downloading"
}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://open.spotify.com/episode/329nSfeZ5vRoJooKsZh9kH",
"status": "Skipped",
"s3_url": null,
"subtitle_url": null,
"error": "Audio-only Spotify episode (DRM protected) - skipped. This episode contains only audio which is Widevine-encrypted and cannot be downloaded.",
"error_type": "warning",
"step": "Downloading"
}
Status Values
| Status | Description |
|---|---|
Pending | Job is in queue, waiting to be processed |
Processing | Job is actively being downloaded/processed |
Completed | Job finished successfully |
Failed | Job encountered a technical error |
Warning | Job failed due to a content issue (private video, members-only, geo-blocked, unavailable) |
Skipped | Job was skipped because the content cannot be processed (e.g. audio-only Spotify episodes with Widevine DRM) |
Cancelled | Job cancelled by the client |
CancelledByAdmin | Job cancelled by an operator |
Processing Steps
| Step | Description |
|---|---|
Queued | Waiting in queue |
Downloading | Downloading video/audio streams |
Muxing | Combining streams with FFmpeg |
Uploading | Uploading to S3 |
Finished | Complete |
Polling Example
import requests
import time
def wait_for_job(job_id, api_key, timeout=600):
start = time.time()
while time.time() - start < timeout:
response = requests.get(
f"https://api.tornadoapi.io/jobs/{job_id}",
headers={"x-api-key": api_key}
)
data = response.json()
print(f"Status: {data['status']} - {data.get('step', 'N/A')}")
if data["status"] == "Completed":
return data["s3_url"]
elif data["status"] == "Failed":
raise Exception(f"Job failed: {data['error']}")
elif data["status"] in ("Skipped", "Cancelled", "CancelledByAdmin"):
print(f"Job skipped: {data['error']}")
return None
time.sleep(3)
raise Exception("Timeout waiting for job")
Success Response
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
"status": "Completed",
"s3_url": "https://cdn.example.com/videos/video.mp4?X-Amz-Algorithm=...",
"subtitle_url": null,
"error": null,
"step": "Finished",
"title": "Rick Astley - Never Gonna Give You Up",
"description": "The official video for Never Gonna Give You Up...",
"release_date": "2009-10-25T00:00:00Z",
"download_speed_mbps": 45.2,
"upload_speed_mbps": 120.5,
"download_duration_ms": 3200,
"mux_duration_ms": 1500,
"upload_duration_ms": 2100,
"total_duration_ms": 8050,
"file_size": 52428800,
"native_video_codec": "avc1",
"native_audio_codec": "mp4a",
"download_strategy": "native",
"download_retries": 1,
"upload_retries": 0,
"queue_wait_ms": 450,
"requested_quality": "best",
"actual_quality": "1080p",
"created_at": 1705507200000,
"finished_at": 1705507260000
}
Error Responses
null
The presigned
s3_url and subtitle_url are valid for 24 hours. Download the files before they expire.Performance and metadata fields (
title, description, release_date, folder, batch_id, download_speed_mbps, upload_speed_mbps, extract_duration_ms, download_duration_ms, mux_duration_ms, upload_duration_ms, total_duration_ms, precheck_duration_ms, io_wait_ms, cpu_wait_ms, upload_wait_ms, subtitle_duration_ms, file_move_ms, file_size, native_video_codec, native_audio_codec, download_strategy, cascade_total_attempts, download_retries, upload_retries, queue_wait_ms, requested_quality, actual_quality, webhook_status, finished_at) are only present for completed/failed jobs that have been persisted to the database. Active jobs (Pending/Processing) return only the base fields (id, url, status, s3_url, subtitle_url, error, error_type, step, created_at). Fields with no value are omitted from the response.Completed jobs also echo back the original request parameters (
format, video_codec, audio_codec, audio_bitrate, video_quality, filename, audio_only, download_subtitles, download_thumbnail, quality_preset, max_resolution, clip_start, clip_end, live_recording, live_from_start, max_duration, wait_for_video, enable_progress_webhook). These are omitted when null.Was this page helpful?