Bulk Create Jobs
curl --request POST \
--url https://api.tornadoapi.io/jobs/bulk \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"jobs": [
{}
],
"jobs[].url": "<string>",
"jobs[].filename": "<string>",
"folder": "<string>",
"format": "<string>",
"video_codec": "<string>",
"audio_codec": "<string>",
"audio_bitrate": "<string>",
"video_quality": 123,
"audio_only": true,
"download_subtitles": true,
"download_thumbnail": true,
"quality_preset": "<string>",
"max_resolution": "<string>",
"clip_start": "<string>",
"clip_end": "<string>",
"live_recording": true,
"live_from_start": true,
"max_duration": 123,
"wait_for_video": true
}
'import requests
url = "https://api.tornadoapi.io/jobs/bulk"
payload = {
"jobs": [{}],
"jobs[].url": "<string>",
"jobs[].filename": "<string>",
"folder": "<string>",
"format": "<string>",
"video_codec": "<string>",
"audio_codec": "<string>",
"audio_bitrate": "<string>",
"video_quality": 123,
"audio_only": True,
"download_subtitles": True,
"download_thumbnail": True,
"quality_preset": "<string>",
"max_resolution": "<string>",
"clip_start": "<string>",
"clip_end": "<string>",
"live_recording": True,
"live_from_start": True,
"max_duration": 123,
"wait_for_video": True
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
jobs: [{}],
'jobs[].url': '<string>',
'jobs[].filename': '<string>',
folder: '<string>',
format: '<string>',
video_codec: '<string>',
audio_codec: '<string>',
audio_bitrate: '<string>',
video_quality: 123,
audio_only: true,
download_subtitles: true,
download_thumbnail: true,
quality_preset: '<string>',
max_resolution: '<string>',
clip_start: '<string>',
clip_end: '<string>',
live_recording: true,
live_from_start: true,
max_duration: 123,
wait_for_video: true
})
};
fetch('https://api.tornadoapi.io/jobs/bulk', 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/bulk",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'jobs' => [
[
]
],
'jobs[].url' => '<string>',
'jobs[].filename' => '<string>',
'folder' => '<string>',
'format' => '<string>',
'video_codec' => '<string>',
'audio_codec' => '<string>',
'audio_bitrate' => '<string>',
'video_quality' => 123,
'audio_only' => true,
'download_subtitles' => true,
'download_thumbnail' => true,
'quality_preset' => '<string>',
'max_resolution' => '<string>',
'clip_start' => '<string>',
'clip_end' => '<string>',
'live_recording' => true,
'live_from_start' => true,
'max_duration' => 123,
'wait_for_video' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"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"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.tornadoapi.io/jobs/bulk"
payload := strings.NewReader("{\n \"jobs\": [\n {}\n ],\n \"jobs[].url\": \"<string>\",\n \"jobs[].filename\": \"<string>\",\n \"folder\": \"<string>\",\n \"format\": \"<string>\",\n \"video_codec\": \"<string>\",\n \"audio_codec\": \"<string>\",\n \"audio_bitrate\": \"<string>\",\n \"video_quality\": 123,\n \"audio_only\": true,\n \"download_subtitles\": true,\n \"download_thumbnail\": true,\n \"quality_preset\": \"<string>\",\n \"max_resolution\": \"<string>\",\n \"clip_start\": \"<string>\",\n \"clip_end\": \"<string>\",\n \"live_recording\": true,\n \"live_from_start\": true,\n \"max_duration\": 123,\n \"wait_for_video\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.tornadoapi.io/jobs/bulk")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"jobs\": [\n {}\n ],\n \"jobs[].url\": \"<string>\",\n \"jobs[].filename\": \"<string>\",\n \"folder\": \"<string>\",\n \"format\": \"<string>\",\n \"video_codec\": \"<string>\",\n \"audio_codec\": \"<string>\",\n \"audio_bitrate\": \"<string>\",\n \"video_quality\": 123,\n \"audio_only\": true,\n \"download_subtitles\": true,\n \"download_thumbnail\": true,\n \"quality_preset\": \"<string>\",\n \"max_resolution\": \"<string>\",\n \"clip_start\": \"<string>\",\n \"clip_end\": \"<string>\",\n \"live_recording\": true,\n \"live_from_start\": true,\n \"max_duration\": 123,\n \"wait_for_video\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tornadoapi.io/jobs/bulk")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"jobs\": [\n {}\n ],\n \"jobs[].url\": \"<string>\",\n \"jobs[].filename\": \"<string>\",\n \"folder\": \"<string>\",\n \"format\": \"<string>\",\n \"video_codec\": \"<string>\",\n \"audio_codec\": \"<string>\",\n \"audio_bitrate\": \"<string>\",\n \"video_quality\": 123,\n \"audio_only\": true,\n \"download_subtitles\": true,\n \"download_thumbnail\": true,\n \"quality_preset\": \"<string>\",\n \"max_resolution\": \"<string>\",\n \"clip_start\": \"<string>\",\n \"clip_end\": \"<string>\",\n \"live_recording\": true,\n \"live_from_start\": true,\n \"max_duration\": 123,\n \"wait_for_video\": true\n}"
response = http.request(request)
puts response.read_body{
"batch_id": "550e8400-e29b-41d4-a716-446655440001",
"total_jobs": 3,
"job_ids": [
"660f9511-f30c-52e5-b827-557766551111",
"660f9511-f30c-52e5-b827-557766551112",
"660f9511-f30c-52e5-b827-557766551113"
]
}
Bulk
Bulk Create Jobs
Create multiple download jobs at once
POST
/
jobs
/
bulk
Bulk Create Jobs
curl --request POST \
--url https://api.tornadoapi.io/jobs/bulk \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"jobs": [
{}
],
"jobs[].url": "<string>",
"jobs[].filename": "<string>",
"folder": "<string>",
"format": "<string>",
"video_codec": "<string>",
"audio_codec": "<string>",
"audio_bitrate": "<string>",
"video_quality": 123,
"audio_only": true,
"download_subtitles": true,
"download_thumbnail": true,
"quality_preset": "<string>",
"max_resolution": "<string>",
"clip_start": "<string>",
"clip_end": "<string>",
"live_recording": true,
"live_from_start": true,
"max_duration": 123,
"wait_for_video": true
}
'import requests
url = "https://api.tornadoapi.io/jobs/bulk"
payload = {
"jobs": [{}],
"jobs[].url": "<string>",
"jobs[].filename": "<string>",
"folder": "<string>",
"format": "<string>",
"video_codec": "<string>",
"audio_codec": "<string>",
"audio_bitrate": "<string>",
"video_quality": 123,
"audio_only": True,
"download_subtitles": True,
"download_thumbnail": True,
"quality_preset": "<string>",
"max_resolution": "<string>",
"clip_start": "<string>",
"clip_end": "<string>",
"live_recording": True,
"live_from_start": True,
"max_duration": 123,
"wait_for_video": True
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
jobs: [{}],
'jobs[].url': '<string>',
'jobs[].filename': '<string>',
folder: '<string>',
format: '<string>',
video_codec: '<string>',
audio_codec: '<string>',
audio_bitrate: '<string>',
video_quality: 123,
audio_only: true,
download_subtitles: true,
download_thumbnail: true,
quality_preset: '<string>',
max_resolution: '<string>',
clip_start: '<string>',
clip_end: '<string>',
live_recording: true,
live_from_start: true,
max_duration: 123,
wait_for_video: true
})
};
fetch('https://api.tornadoapi.io/jobs/bulk', 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/bulk",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'jobs' => [
[
]
],
'jobs[].url' => '<string>',
'jobs[].filename' => '<string>',
'folder' => '<string>',
'format' => '<string>',
'video_codec' => '<string>',
'audio_codec' => '<string>',
'audio_bitrate' => '<string>',
'video_quality' => 123,
'audio_only' => true,
'download_subtitles' => true,
'download_thumbnail' => true,
'quality_preset' => '<string>',
'max_resolution' => '<string>',
'clip_start' => '<string>',
'clip_end' => '<string>',
'live_recording' => true,
'live_from_start' => true,
'max_duration' => 123,
'wait_for_video' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"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"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.tornadoapi.io/jobs/bulk"
payload := strings.NewReader("{\n \"jobs\": [\n {}\n ],\n \"jobs[].url\": \"<string>\",\n \"jobs[].filename\": \"<string>\",\n \"folder\": \"<string>\",\n \"format\": \"<string>\",\n \"video_codec\": \"<string>\",\n \"audio_codec\": \"<string>\",\n \"audio_bitrate\": \"<string>\",\n \"video_quality\": 123,\n \"audio_only\": true,\n \"download_subtitles\": true,\n \"download_thumbnail\": true,\n \"quality_preset\": \"<string>\",\n \"max_resolution\": \"<string>\",\n \"clip_start\": \"<string>\",\n \"clip_end\": \"<string>\",\n \"live_recording\": true,\n \"live_from_start\": true,\n \"max_duration\": 123,\n \"wait_for_video\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.tornadoapi.io/jobs/bulk")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"jobs\": [\n {}\n ],\n \"jobs[].url\": \"<string>\",\n \"jobs[].filename\": \"<string>\",\n \"folder\": \"<string>\",\n \"format\": \"<string>\",\n \"video_codec\": \"<string>\",\n \"audio_codec\": \"<string>\",\n \"audio_bitrate\": \"<string>\",\n \"video_quality\": 123,\n \"audio_only\": true,\n \"download_subtitles\": true,\n \"download_thumbnail\": true,\n \"quality_preset\": \"<string>\",\n \"max_resolution\": \"<string>\",\n \"clip_start\": \"<string>\",\n \"clip_end\": \"<string>\",\n \"live_recording\": true,\n \"live_from_start\": true,\n \"max_duration\": 123,\n \"wait_for_video\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tornadoapi.io/jobs/bulk")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"jobs\": [\n {}\n ],\n \"jobs[].url\": \"<string>\",\n \"jobs[].filename\": \"<string>\",\n \"folder\": \"<string>\",\n \"format\": \"<string>\",\n \"video_codec\": \"<string>\",\n \"audio_codec\": \"<string>\",\n \"audio_bitrate\": \"<string>\",\n \"video_quality\": 123,\n \"audio_only\": true,\n \"download_subtitles\": true,\n \"download_thumbnail\": true,\n \"quality_preset\": \"<string>\",\n \"max_resolution\": \"<string>\",\n \"clip_start\": \"<string>\",\n \"clip_end\": \"<string>\",\n \"live_recording\": true,\n \"live_from_start\": true,\n \"max_duration\": 123,\n \"wait_for_video\": true\n}"
response = http.request(request)
puts response.read_body{
"batch_id": "550e8400-e29b-41d4-a716-446655440001",
"total_jobs": 3,
"job_ids": [
"660f9511-f30c-52e5-b827-557766551111",
"660f9511-f30c-52e5-b827-557766551112",
"660f9511-f30c-52e5-b827-557766551113"
]
}
Overview
Create up to 100 download jobs in a single API request. All jobs share the same encoding options but can have individual filenames. The response includes a groupingbatch_id and individual job_ids; follow each job with GET /jobs/{id}.
Use this endpoint for high-volume submission: a bulk request counts as one HTTP request toward the API’s 1,000 requests per second per authenticated client ceiling. All direct API keys in the same organization share that budget across API instances. If you receive 429 with error: "rate_limit_exceeded", respect Retry-After before retrying. Bulk requires a direct API key; it is not available through marketplace authentication. Storage allowances and server capacity checks still apply.
Header Parameters
string
required
Your API key for authentication
Request
array
required
Array of job items (max 100)
Job Item
string
required
Video URL to download
string
Custom filename for this specific job
Shared Options
string
S3 folder prefix for all jobs
string
Output format for all jobs. Video:
mp4, mkv, webm, mov. Audio: m4a, mp3, ogg, opus. Default: mp4 (or m4a when audio_only is true)string
Video codec for all jobs:
copy, h264, h265, vp9string
Audio codec for all jobs:
copy, aac, opus, mp3string
Audio bitrate for all jobs:
64k, 128k, 192k, 256k, 320kinteger
Video quality CRF for all jobs (0-51)
boolean
default:"false"
Extract audio only for all jobs. Outputs
m4a by default (no re-encoding). Set format to mp3, ogg, or opus for other audio formats.boolean
default:"false"
Download subtitles for all jobs
boolean
default:"false"
Download thumbnails for all jobs
string
Quality preset for all jobs:
highest, high, medium, low, loweststring
Video resolution preference for all jobs:
best, lowest, 2160, 1440, 1080, 720, 480, 360, 240, 144. A numeric value chooses the best available resolution at or below the value, or the smallest available format if none fits. lowest always requests the smallest available video format. See single-job resolution and audio selection for details.string
Start timestamp for video clipping (all jobs). Format:
HH:MM:SS or seconds.string
End timestamp for video clipping (all jobs). Format:
HH:MM:SS or seconds.boolean
default:"false"
Enable live stream recording mode for all jobs.
boolean
default:"false"
Record from stream beginning (VOD mode) for all live stream jobs.
integer
Maximum recording duration in seconds for all jobs.
boolean
default:"false"
Wait for scheduled streams to start for all jobs.
Request Example
{
"jobs": [
{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"filename": "never-gonna-give-you-up"
},
{
"url": "https://www.youtube.com/watch?v=9bZkp7q19f0",
"filename": "gangnam-style"
},
{
"url": "https://www.youtube.com/watch?v=kJQP7kiw5Fk"
}
],
"folder": "music-videos",
"format": "mp4",
"max_resolution": "1080",
"video_codec": "h264",
"audio_codec": "aac",
"audio_bitrate": "192k"
}
Each item in the
jobs array only requires a url. The filename is optional - if not provided, the original video title will be used.All other options (folder, format, video_codec, etc.) are applied to all jobs in the batch.Response
string
Batch ID for tracking all jobs
integer
Number of jobs created
array
List of individual job IDs
Examples
curl -X POST "https://api.tornadoapi.io/jobs/bulk" \
-H "x-api-key: sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"jobs": [
{"url": "https://youtube.com/watch?v=video1", "filename": "my-first-video"},
{"url": "https://youtube.com/watch?v=video2", "filename": "my-second-video"},
{"url": "https://youtube.com/watch?v=video3"}
],
"folder": "my-downloads",
"format": "mp4"
}'
const response = await fetch('https://api.tornadoapi.io/jobs/bulk', {
method: 'POST',
headers: {
'x-api-key': 'sk_your_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
jobs: [
{url: 'https://youtube.com/watch?v=video1', filename: 'my-first-video'},
{url: 'https://youtube.com/watch?v=video2', filename: 'my-second-video'},
{url: 'https://youtube.com/watch?v=video3'}
],
folder: 'my-downloads',
format: 'mp4'
})
});
const data = await response.json();
console.log(`Created ${data.total_jobs} jobs`);
console.log(`Batch ID: ${data.batch_id}`);
import requests
response = requests.post(
'https://api.tornadoapi.io/jobs/bulk',
headers={'x-api-key': 'sk_your_api_key'},
json={
'jobs': [
{'url': 'https://youtube.com/watch?v=video1', 'filename': 'my-first-video'},
{'url': 'https://youtube.com/watch?v=video2', 'filename': 'my-second-video'},
{'url': 'https://youtube.com/watch?v=video3'}
],
'folder': 'my-downloads',
'format': 'mp4'
}
)
data = response.json()
print(f"Created {data['total_jobs']} jobs")
curl -X POST "https://api.tornadoapi.io/jobs/bulk" \
-H "x-api-key: sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"jobs": [
{"url": "https://youtube.com/watch?v=song1"},
{"url": "https://youtube.com/watch?v=song2"},
{"url": "https://youtube.com/watch?v=song3"}
],
"folder": "music",
"audio_only": true,
"audio_bitrate": "320k"
}'
Success Response
{
"batch_id": "550e8400-e29b-41d4-a716-446655440001",
"total_jobs": 3,
"job_ids": [
"660f9511-f30c-52e5-b827-557766551111",
"660f9511-f30c-52e5-b827-557766551112",
"660f9511-f30c-52e5-b827-557766551113"
]
}
Error Responses
{
"error": "No jobs provided"
}
{
"error": "Maximum 100 jobs per bulk request"
}
{
"error": "Invalid folder name: path traversal not allowed"
}
{
"error": "Missing x-api-key header"
}
Notes
- Maximum 100 jobs per bulk request
- All jobs share the same encoding options and folder
- Each job can have a unique filename
- Webhooks are not supported in bulk requests (use individual job creation for webhooks)
- Use
/jobs/{id}to poll individual job status - The batch_id is for reference only - jobs are processed independently
Was this page helpful?