API documentation

Create downloads, read YouTube metadata, poll progress, or create subtitle files. The complete OpenAPI files stay available for generated clients.

First request

GET

One standard MP3 job for $0.00020 from your balance. Extended duration stays off, so no duration multiplier is charged.

curl --get "https://p.savenow.to/ajax/download.php" \
    --data-urlencode "url=SOURCE_VIDEO_URL" \
    --data "format=mp3" \
    --data "apikey=YOUR_API_KEY" \
    --data "allow_extended_duration=0"

Replace before you run

SOURCE_VIDEO_URL
Replace it with one public video link up to 180 minutes long. A longer YouTube video is rejected with a duration error and is not charged.
YOUR_API_KEY
Replace it with the key from your dashboard. Run the command in a terminal or on your server, never in browser code.
  1. Check your balance first

    The dashboard balance must cover $0.00020. After you confirm your email, eligible new accounts can get a one-time test credit; the dashboard shows whether it was added. Otherwise add balance. "Not enough balance" means no job was created.

    Read the free-start guide
  2. Send it once and keep the id

    A success response returns an id. Do not send the command again for the same test, because every new request creates a new billable job.

  3. Poll progress until 1000

    Call https://p.savenow.to/ajax/progress.php?id=ID. Below 1000 the job is still running. At 1000 the file is ready only when success is 1 or true and download_url is not empty. Otherwise stop and show text or message.

Complete MP3 script with polling

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"],
})

Continue building

OpenAPI docs

Download YAML or JSON specs and inspect downloader, progress, and subtitle endpoints.

Read docs

Embed resources

Keep button, card, widget, and REST paths reachable for existing users and migrations.

View resources