Download endpoint
Submit a video URL and output format. The API accepts the job immediately and returns an id.
Poll /ajax/progress.php with the returned id. Progress below 1000 is pending regardless of success. At 1000, use download_url only when success is 1 or true and the URL is non-empty; otherwise stop and surface text or message, with a generic failure message if neither is present.
cURL request
curl --get "https://p.savenow.to/ajax/download.php" \
--data-urlencode "url=SOURCE_VIDEO_URL" \
--data "format=mp44k" \
--data "apikey=YOUR_API_KEY" \
--data "add_info=1" \
--data "allow_extended_duration=1" \
--data "no_merge=0"
JavaScript request
const params = new URLSearchParams({
url: 'SOURCE_VIDEO_URL',
format: 'mp44k',
apikey: 'YOUR_API_KEY',
add_info: '1',
allow_extended_duration: '1',
no_merge: '0',
});
const response = await fetch('https://p.savenow.to/ajax/download.php?' + params);
if (!response.ok) throw new Error(`Download request failed: ${response.status}`);
const { id } = await response.json();
const progressUrl = 'https://p.savenow.to/ajax/progress.php?' + new URLSearchParams({ id });
console.log({ id, progressUrl });
Python request
Requires the requests package: python -m pip install requests
import requests
params = {
"url": "SOURCE_VIDEO_URL",
"format": "mp44k",
"apikey": "YOUR_API_KEY",
"add_info": "1",
"allow_extended_duration": "1",
"no_merge": "0",
}
response = requests.get("https://p.savenow.to/ajax/download.php", params=params, timeout=30)
response.raise_for_status()
download = response.json()
download_id = download["id"]
progress_url = requests.Request(
"GET",
"https://p.savenow.to/ajax/progress.php",
params={"id": download_id},
).prepare().url
print({"id": download_id, "progress_url": progress_url})
Video Info endpoint
Read available metadata for a YouTube URL without creating a media download job.
Use each returned metadata field only when present. This endpoint responds directly and does not return a download job id or require progress polling.
cURL request
curl --get "https://p.savenow.to/ajax/api.php" \
--data "function=i" \
--data "api=YOUR_API_KEY" \
--data-urlencode "u=SOURCE_VIDEO_URL"
JavaScript request
const params = new URLSearchParams({
function: 'i',
api: process.env.VDA_API_KEY || '',
u: 'SOURCE_VIDEO_URL',
});
if (!params.get('api')) throw new Error('Set VDA_API_KEY on the server.');
const response = await fetch('https://p.savenow.to/ajax/api.php?' + params);
if (!response.ok) throw new Error(`Info request failed: ${response.status}`);
const info = await response.json();
console.log({ title: info.title, duration: info.duration, thumbnail: info.thumbnail });
Python request
Requires the requests package: python -m pip install requests
import os
import requests
params = {
"function": "i",
"api": os.environ["VDA_API_KEY"],
"u": "SOURCE_VIDEO_URL",
}
response = requests.get("https://p.savenow.to/ajax/api.php", params=params, timeout=30)
response.raise_for_status()
info = response.json()
print({"title": info.get("title"), "duration": info.get("duration"), "thumbnail": info.get("thumbnail")})
Progress endpoint
Poll a download or subtitle job and read its terminal outcome.
If progress is below 1000, keep polling regardless of success. At 1000, use download_url only when success is 1 or true and the URL is non-empty; otherwise stop and surface text or message, with a generic failure message if neither is present. Treat non-2xx responses as request errors.
cURL request
curl --get "https://p.savenow.to/ajax/progress.php" \
--data "id=DOWNLOAD_ID"
JavaScript request
const params = new URLSearchParams({ id: 'DOWNLOAD_ID' });
const response = await fetch('https://p.savenow.to/ajax/progress.php?' + params, { redirect: 'manual' });
if (!response.ok) throw new Error(`Progress request failed: ${response.status}`);
const result = await response.json();
if (result.progress < 1000) {
console.log('pending', result.progress);
} else if ((result.success === 1 || result.success === true) && typeof result.download_url === 'string' && result.download_url.trim() !== '') {
console.log('ready', result.download_url);
} else {
throw new Error(`${result.text || result.message || 'Terminal job failed.'} (no usable download_url)`);
}
Python request
Requires the requests package: python -m pip install requests
import requests
response = requests.get(
"https://p.savenow.to/ajax/progress.php",
params={"id": "DOWNLOAD_ID"},
timeout=30,
allow_redirects=False,
)
if not 200 <= response.status_code < 300:
raise requests.HTTPError(f"Progress request failed: {response.status_code}", response=response)
result = response.json()
if result["progress"] < 1000:
print({"state": "pending", "progress": result["progress"]})
elif result.get("success") in (1, True) and isinstance(result.get("download_url"), str) and result["download_url"].strip():
print({"state": "ready", "download_url": result["download_url"]})
else:
raise RuntimeError(f"{result.get('text') or result.get('message') or 'Terminal job failed.'} (no usable download_url)")
Subtitles endpoint
Submit a subtitle or transcript job for a video URL.
Poll the returned progress_url directly. Below progress 1000, keep polling regardless of success. At 1000, use download_url only with success=1 or true and a non-empty URL. The URL points to the requested .vtt or .srt file. Otherwise stop and surface text or message, with a generic failure message if neither is present. Treat non-2xx responses as request errors.
cURL request
curl --get "https://p.savenow.to/ajax/subtitles" \
--data-urlencode "url=SOURCE_VIDEO_URL" \
--data "api=YOUR_API_KEY" \
--data "lang=en" \
--data "format=vtt"
JavaScript request
const params = new URLSearchParams({
url: 'SOURCE_VIDEO_URL',
api: 'YOUR_API_KEY',
lang: 'en',
format: 'vtt',
});
const response = await fetch('https://p.savenow.to/ajax/subtitles?' + params);
if (!response.ok) throw new Error(`Subtitle request failed: ${response.status}`);
const { id, progress_url: progressUrl } = await response.json();
console.log({ id, progressUrl });
Python request
Requires the requests package: python -m pip install requests
import requests
params = {
"url": "SOURCE_VIDEO_URL",
"api": "YOUR_API_KEY",
"lang": "en",
"format": "vtt",
}
response = requests.get("https://p.savenow.to/ajax/subtitles", params=params, timeout=30)
response.raise_for_status()
subtitles = response.json()
print({
"id": subtitles["id"],
"progress_url": subtitles["progress_url"],
})