SPVD API Guide

Fill these in to personalise every example on this page. Nothing is sent anywhere.

SPVD API guide

Extract downloadable media and rich metadata from YouTube, Instagram, Facebook, TikTok, Dailymotion and Pinterest, and turn separate video and audio streams into a single ready-to-play MP4.

Overview

SPVD is a REST API. You send a GET request with a video ID, URL or search query, and get back JSON with:

  • Media: direct links to every available video, audio and image quality, with size, resolution and codec details.
  • Render configs: optional links that ask our rendering servers to merge the best video and audio into one MP4 (or extract audio), hosted for you to download.
  • Metadata: titles, authors, statistics, comments, playlists, channel pages, search results and transcripts.

Quickstart

  1. Subscribe and copy your key. Pick a plan on the API marketplace and copy your API key and host from the endpoint playground.
  2. Make your first call. Fetch a YouTube video's details:
    const res = await fetch("https://api-host/youtube/v3/video/details?videoId=dQw4w9WgXcQ", {
      headers: { "X-RapidAPI-Key": "YOUR_API_KEY", "X-RapidAPI-Host": "api-host" },
    });
    const { error, contents, metadata } = await res.json();
    
    if (error) throw new Error(error.message);
    console.log(metadata.title, contents[0].videos.length, "video formats");
  3. Pick a file. Choose an item from contents[0].videos (or audios) and download its url, or request a rendered MP4 if you need video and audio in one file.

Authentication

Every data endpoint needs the two headers issued by the API marketplace:

X-RapidAPI-Key: YOUR_API_KEY
X-RapidAPI-Host: api-host
Keep your key server-side.

Anyone with your key can spend your quota. Call the API from your backend and pass only the results to browsers or apps.

These need no key: the health check, the OpenAPI specs, and the render URLs inside a renderConfig (the URL itself is the credential).

Making requests

  • Base URL: https://api-host — every path starts with the platform and version, e.g. /youtube/v3/…, /tiktok/v3/….
  • Method: GET, parameters in the query string. URL-encode values, especially url parameters (encodeURIComponent in JS, params= in Python requests).
  • Lists: multi-value parameters are comma-separated: renderableFormats=720p,1080p.
  • Timeouts: most calls answer in 1–5 seconds, but a cold video with many formats can take longer. Use a client timeout of at least 60 seconds.
  • Freshness: identical requests may be served from a short-lived cache (a couple of minutes), so repeated calls are fast and consistent.
GET https://api-host/facebook/v3/post/details?url=https%3A%2F%2Fwww.facebook.com%2Fwatch%2F%3Fv%3D1234567890

Response format

All data endpoints return the same envelope. Full schema: ExtractionResult.

{
  "error": null,                       // or { "message", "code", "statusCode" }
  "contents": [                        // downloadable media (absent for metadata-only endpoints)
    {
      "videos":           [ /* MediaItem */ ],
      "audios":           [ /* MediaItem */ ],
      "images":           [ /* MediaItem */ ],
      "renderableVideos": [ /* RenderableMediaItem */ ],
      "renderableAudios": [ /* RenderableMediaItem */ ]
    }
  ],
  "metadata": {                        // non-file information
    "title": "Never Gonna Give You Up",
    "thumbnailUrl": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
    "author": { },
    "additionalData": { }
  },
  "notes": ["Endpoint cost: 1 credit."]
}
Always check error first.

Extraction failures (private post, removed video, bad ID…) are reported inside the body, often with HTTP 200. Don't rely on the status code alone.

A media item

{
  "label": "720p",
  "url": "https://…",                  // temporary link
  "repId": "5f1c…",                    // stable id for this quality
  "metadata": {
    "mime_type": "video/mp4; codecs=\"avc1.4d401f\"",
    "width": 1280,
    "height": 720,
    "has_audio": false,
    "content_length": 47395612,
    "content_length_text": "45.2 MB",
    "fps": 30,
    "bitrate": 1512000
  }
}

Downloading media

Direct links

  • Links are temporary. Download soon after you receive them. If a link returns 403, 404 or 410, call the endpoint again for fresh links. Don't store links in a database.
  • Video-only vs. muxed. On YouTube and Facebook, qualities above 360p/SD are usually video-only (has_audio: false), with audio in audios. Either combine them yourself or use rendering.
  • Range requests are supported by most links, so you can resume or stream in chunks.
  • Dailymotion videos are HLS playlists (.m3u8). Use a player that supports HLS, or a render config to get an MP4.

YouTube urlAccess

ValueUse it whenCost
normal (default)You download from a server or device that YouTube serves normally. Fastest.1 credit
proxiedDirect links return 403 in your environment (some cloud providers and regions). Links are relayed through the API; slower but reliable.2 credits

Server-side rendering

Rendering produces a single, ready-to-play file that the platform doesn't offer directly: best video merged with audio (YouTube), a DASH stream turned into an MP4 (Instagram, Facebook), an HLS stream converted to MP4 (Dailymotion), or extracted audio (TikTok, Dailymotion). The file is hosted temporarily for you to download.

  1. Ask for renderable formats. Add renderableFormats to a details request, e.g. renderableFormats=1080p,720p or highres.
  2. Pick an item. Each entry in renderableVideos is either a success (has renderConfig) or an error (has error, e.g. over your plan's size limit).
  3. Start the render. GET the renderConfig.executionUrl. No API key is needed. Calling it again later is safe: you'll re-attach to the same job.
  4. Watch progress. Open statusUrl (WebSocket) or sseStatusUrl (Server-Sent Events). Events arrive about once per second until status is done or failed.
  5. Download. When done, download output.url before expires_at.
// 1) Ask for a rendered 1080p file
const res = await fetch("https://api-host/youtube/v3/video/details?videoId=dQw4w9WgXcQ&renderableFormats=1080p", {
  headers: { "X-RapidAPI-Key": "YOUR_API_KEY", "X-RapidAPI-Host": "api-host" },
});
const data = await res.json();
if (data.error) throw new Error(data.error.message);

// 2) Pick a successful renderable item
const item = data.contents[0].renderableVideos?.find((r) => r.renderConfig);
if (!item) throw new Error("No renderable format available");

// 3) Start the job (no API key needed)
await fetch(item.renderConfig.executionUrl);

// 4) Follow progress until done
const fileUrl = await new Promise((resolve, reject) => {
  const ws = new WebSocket(item.renderConfig.statusUrl);
  ws.onmessage = (e) => {
    const job = JSON.parse(e.data);
    console.log(job.status, job.progress + "%");
    if (job.status === "done") { resolve(job.output.url); ws.close(); }
    if (job.status === "failed" || job.status === "not_found") { reject(new Error(job.error)); ws.close(); }
  };
  ws.onerror = reject;
});

// 5) Download
console.log("Download:", fileUrl);

Rendering is included in the request that returned the render config (see credits). Starting, watching and downloading the job costs nothing extra.

The three render URLs

URLHow to call itWhat you get
executionUrlGET, no headersStarts the render (or re-attaches to it) and returns the job id. Details.
statusUrlWebSocketA status event about every second until the job ends. Details.
sseStatusUrlGET as Server-Sent EventsThe same events, for clients without WebSockets. Details.

Treat these URLs as opaque: use them exactly as returned. They need no API key because the URL itself is the credential, so don't publish them.

Job statuses

StatusMeaning
pendingAccepted, about to start.
queuedWaiting for capacity. Starts automatically; keep listening.
downloading_inputsFetching the source streams (progress 0–50).
processingMerging / converting (progress 50–60).
uploading_outputStoring the file for download (progress 60–100).
doneFinished. output.url is ready. Final.
failedStopped; see error. Final.
not_foundThe job was never started or has expired. Final.

Good to know

  • Safe to repeat. Calling executionUrl again attaches to the running job, or returns the finished file instantly. A failed or stalled job is restarted.
  • Reconnecting is fine. Jobs keep running whether or not anyone listens. If the socket drops, open the status URL again; a finished job immediately sends its final event.
  • Expiry. The file can be downloaded until expires_at (1–2 hours depending on your plan). After that the status streams report not_found.
  • Start soon. Render configs point at temporary source links. Start the render within a few minutes; if it fails because a source expired, request the details endpoint again for a fresh renderConfig.
  • Timing. Seconds for short clips, typically 30–120 seconds for long HD videos, longer when queued.
FailureWhat to do
A source link expired or was refused.Call the details endpoint again and start the new executionUrl.
The file is larger than your plan allows.Choose a smaller quality or use highres, which respects your limit.
Timeout / incomplete download.Call the same executionUrl again; the job restarts.
Service at capacity (“try again later”).Wait a few seconds and call executionUrl again.

Choosing renderable formats

You can name exact qualities or let the API choose:

You sendYou get
1080pExactly 1080p, if available and within your plan's size limit.
highresThe best quality that fits your plan. Recommended default.
lowres / midresSmallest / middle quality that fits your plan: great for previews and mobile.
allEvery resolution, smallest first, up to your plan's count.
all_highresEvery resolution, largest first, up to your plan's count.
1080p,720p,highresCombinations work; each value produces one item.

Full lists per platform: RenderableFormat.

Field filtering

YouTube video details accepts fields to return only what you need. Smaller responses are faster, and skipping contents avoids preparing download links at all.

ExampleEffect
fields=metadataMetadata only; no media links.
fields=contents.videos,contents.audiosOnly the video and audio lists.
fields=-metadata.additionalDataEverything except the large additionalData block.
fields=-contents,-metadata.transcriptExclude several parts.

Rules: when any field is listed without -, only listed fields are returned (whitelist). When every field starts with -, everything except those is returned (blacklist). Listing a child (contents.videos) automatically includes its parent. Allowed values: Field.

Pagination

Comments, playlists, channel tabs, hashtags and search results are paginated. Each page includes a nextToken; send it back unchanged with the same other parameters to get the next page. It is null on the last page.

async function* channelVideos(channelId) {
  let nextToken;
  do {
    const qs = new URLSearchParams({ channelId, contentType: "videos", ...(nextToken && { nextToken }) });
    const res = await fetch(`https://api-host/youtube/v3/channel/videos?${qs}`, {
      headers: { "X-RapidAPI-Key": "YOUR_API_KEY", "X-RapidAPI-Host": "api-host" },
    });
    const { error, metadata } = await res.json();
    if (error) throw new Error(error.message);
    yield* metadata.channel.videos.contents;
    nextToken = metadata.channel.videos.nextToken;
  } while (nextToken);
}

for await (const video of channelVideos("UCX6OQ3DkcsbYNE6H8uQQuVA")) {
  console.log(video);
}

Tokens expire after about 15 minutes and are tied to the original query. An expired or altered token returns invalid_params / invalid_token; restart from the first page.

Language & region

ParameterEffect
langLanguage for localized text (titles, labels, relative dates) where the platform supports it. Two-letter code: en, es, fr, de, pt
countryCodeFetch as if from this country. Useful for geo-restricted videos and regional results. Two-letter code or worldwide.

Lists of supported codes: countries & languages. On Dailymotion, geo-restricted videos are retried from an allowed country automatically when you don't send countryCode.

Transcripts

Add getTranscript=true to YouTube video details. The available languages are always listed in metadata.transcript.languages (even without getTranscript), so a common pattern is:

  1. Call video details once and read metadata.transcript.languages.
  2. Call again with getTranscript=true&transcriptLanguage=<one of those values>. Add fields=metadata.transcript to keep the response small.

If a transcript can't be fetched, metadata.transcript.error is set while the rest of the response is still returned.

Credits & billing

RequestCredits
Most endpoints1
YouTube video details, standard1
YouTube video details with urlAccess=proxied or at least one render config returned2 (never more)
Instagram profile details, Instagram user ID from username5 (successful requests)
Starting, watching and downloading a render (render URLs)0

The notes array in the response tells you what a request cost.

Plans & limits

Plans differ in how many formats you can render per request, how large each rendered file may be, and how long rendered files stay available.

PlanRenderable formats per requestMax size per renderRendered file available for
BASIC1500 MB1 hour
PRO31.5 GB1 hour
ULTRA62.5 GB2 hours
MEGA63.5 GB2 hours

Request quotas and rate limits are those shown on the API marketplace for your plan. Exceeding them returns HTTP 429 from the marketplace.

Errors

HTTP status codes

StatusMeaning
200Request processed. Check error in the body: extraction errors are usually returned with 200.
400A query parameter failed validation (unknown enum value, bad country code…). The body names the parameter.
401 / 403Missing or invalid API key, or no active subscription.
404Unknown path.
429Rate limit or quota exceeded.
5xxTemporary problem. Retry with backoff.

Error codes

error.codeWhat it meansWhat to do
params_not_foundA required parameter is missing.Check the endpoint's required parameters.
invalid_paramsA value is malformed (bad ID, wrong contentType, expired nextToken…).Fix the value; restart pagination if needed.
invalid_url / invalid_platform_urlThe url isn't a valid link, or belongs to another platform.Send a full https:// link for the right platform.
invalid_tokenPagination token is invalid or expired.Start again without nextToken.
invalid_user_detailsSubscription details could not be read.Call through the marketplace with valid headers.
not_foundThe content doesn't exist or was removed.Verify the ID/URL.
unavailableThe content exists but can't be accessed (private, region-locked, members-only).Try another countryCode, or skip.
signin_requiredThe platform requires a signed-in user for this content.Not supported; skip.
age_restrictedAge-restricted content.Not supported; skip.
token_expiredA signed link or token has expired.Request fresh data.
non_2xx_response / connection_error / unexpected_redirectThe source platform didn't respond as expected.Retry after a short delay.
operation_timeoutThe source took too long.Retry after a short delay.
unsupported_proxy_for_geo_targetingThe requested countryCode can't be used for this request.Try another country or omit it.
invalid_response_dataThe platform returned data we couldn't understand.Retry; report it if it persists.
unexpected_error / otherSomething went wrong on our side.Retry with backoff; contact support with the request details if it persists.

OpenAPI spec

Machine-readable OpenAPI 3 documents are served by the API itself. They are free and need no key:

URLContents
GET /openapiAll platforms in one document, tags grouped per platform.
GET /openapi/platformsList of available per-platform documents.
GET /openapi/{platform}One platform: youtube, instagram, facebook, tiktok, dailymotion, pinterest, or openapi (these spec routes themselves).
import { writeFile } from "node:fs/promises";

const spec = await (await fetch("https://api-host/openapi")).json();
await writeFile("spvd.openapi.json", JSON.stringify(spec, null, 2));

Import the file into Postman, Insomnia, Bruno, or a generator such as openapi-generator to get a typed client.

Best practices

  • Handle error on every response, and treat each renderableVideos item individually: some may succeed while others report an error.
  • Retry smartly. Retry 5xx, 429, connection_error, non_2xx_response and operation_timeout with exponential backoff (for example 1s, 2s, 4s). Don't retry params_not_found, invalid_*, not_found or signin_required.
  • Request only what you need with fields, and ask for render configs only when the user will actually download.
  • Prefer highres over hard-coded qualities: it adapts to what the video offers and to your plan's limits.
  • Download promptly. Media links and rendered files are temporary.
  • Don't parse IDs from our links. All URLs we return are opaque and may change format at any time.

FAQ

Why does my 1080p YouTube download have no sound?

High qualities are delivered as separate video and audio streams (has_audio: false). Use renderableFormats=1080p to get a merged MP4, or combine the streams yourself.

A link worked yesterday and now returns 403/410.

Links are temporary by design. Call the endpoint again for fresh ones.

I only get 403 on YouTube links from my server.

Some networks are blocked by YouTube. Use urlAccess=proxied.

My renderable item says “beyond the current subscription limit”.

You asked for more formats than your plan renders per request. Ask for fewer, or upgrade.

My renderable item says “No available video … within the subscription size limit”.

That quality doesn't exist for this video or is larger than your plan allows. Try highres, which picks the best allowed size.

Can I download private or members-only content?

No. Only publicly accessible content is supported.

Can I call the API from a browser or mobile app?

Technically yes (CORS is enabled), but your key would be exposed. Proxy requests through your own backend.