FastSaverAPI moved. The API and dashboard now live at api.fastsaver.iowhat changed.

RuTube downloader API without a proxy or a VPN

The RuTube downloader API resolves any public rutube.ru video with a single GET request — a direct video URL, thumbnail, duration and caption as JSON, for 3 credits a call. The geo-sensitive part of reaching RuTube happens on our infrastructure, so your workers run wherever you like. It is the same call you already make for Instagram or TikTok.

Last updated

Endpoint
GET /fetch
Credits
3 per link, 0.1 if it fails
Auth
X-Api-Key header
Returns
direct media URL + metadata

audience

who actually needs this

RuTube is where much of the Russian-speaking internet watches video, and it is nearly invisible in tooling built elsewhere. If your product has users across Russia, Belarus, Kazakhstan or the wider CIS, RuTube links will arrive whether you planned for them or not.

  • Telegram bots and chat apps — users paste whatever they were watching; a bot that shrugs looks broken.
  • Aggregators and schedulers — mirroring a creator's uploads breaks when one platform is unreachable.
  • Archival and monitoring tools — research and newsrooms need the file, not a page that may vanish.
  • Localised products — for many apps RuTube is the platform their audience uses, not a long-tail extra.

None of that is exotic — the same product shapes people build on Instagram or TikTok, for an audience that happens to be elsewhere.

the hard part

why RuTube is awkward to fetch yourself

RuTube does not hand you an MP4. Video arrives as a segmented HLS stream, and access to the playlist is geo-sensitive: the same code run from a US or EU cloud region gets a different answer, sometimes none at all. That is an infrastructure problem, not a parsing problem — which is why most general-purpose downloaders list every platform except this one.

We absorb it: egress, retries, segment handling and the churn when the player changes. What crosses the wire to you is a plain HTTPS URL you can GET from anywhere. One caveat — do not hardcode or pattern-match that URL's hostname. It is not guaranteed to be the same on the next request.

usage

one GET, one link

No SDK, no body, no per-platform branch. You pass the video link and read the JSON.

GET https://api.fastsaver.io/v1/fetch 3 credits
request
curl "https://api.fastsaver.io/v1/fetch?url=https%3A%2F%2Frutube.ru%2Fvideo%2Fd1c0a7f0b4e94a2c8f5b6d3e1a7c9b02%2F" \
  -H "X-Api-Key: fs_sk_•••••••••••"
200 OK · response
{
  "ok": true,
  "id": "d1c0a7f0b4e94a2c8f5b6d3e1a7c9b02",
  "source": "rutube.ru",
  "type": "video",
  "download_url": "https://...",
  "thumbnail_url": "https://...",
  "width": null,
  "height": null,
  "duration": 843,
  "caption": "Тест-драйв нового кроссовера"
}

Those two nulls are not a quirk of this example: a RuTube video response never carries pixel dimensions. Everything else is populated on a normal public video — and a RuTube photo post answers with a shorter object than this one, which the field notes below spell out.

Percent-encode the url value, as above. RuTube share links often carry a tracking query string, and an unescaped & reads as the start of your next parameter — the request still returns 200, just for the wrong link.

reference

the four fields that behave differently here

The envelope is the shared one: ok, source, thumbnail_url, duration and caption mean exactly what they mean on every other platform, and the Instagram downloader API page documents them row by row. Rather than repeat that table, here is only what a RuTube link does differently.

FieldTypeOn RuTube
idstringRuTube's own 32-character hex identifier — the one in the /video/…/ path. Use it as the primary key in your cache.
typestringvideo on a normal upload, image on a RuTube photo post. Never album — there is no items array to walk, so the branch you wrote for Instagram carousels never fires here — but do not hardcode video either.
download_urlstringA plain HTTPS URL you can GET from any region — not a RuTube CDN link. Time-limited, and the hostname can change between calls. On an image response it points at the picture itself.
width / heightnullAlways null on a video, and absent altogether on an image. RuTube never reports pixel dimensions, so size a player from the file or the poster image instead.

That image case is uncommon, but it is the one shape that changes which keys exist rather than which values they hold. A photo post carries download_url, thumbnail_url — the same URL, there is no separate poster — and caption, and no width, height or duration at all. Not null: missing. Code that reads meta["duration"] straight out of the object raises on it.

json · an image response, in full
{
  "ok": true,
  "id": "c4f21a8e7b3d40559ae6182fbb7c0d31",
  "source": "rutube.ru",
  "type": "image",
  "download_url": "https://...",
  "thumbnail_url": "https://...",
  "caption": "Кадр со съёмок"
}

Read everything except ok and type defensively. Branch on type first, then let coverage vary upload to upload — a missing caption is normal, not a failure. Use .get() with a default rather than indexing straight into the object.

integration

adding RuTube to code you already wrote

There is no RuTube client, no RuTube-shaped response, no second code path. If your service already calls /fetch, support here is a hostname in a set. Below is a complete resolve-and-save worker; the RuTube-specific part is one line.

python · resolve and stream to disk
import requests
from urllib.parse import urlparse

FETCH = "https://api.fastsaver.io/v1/fetch"
HEADERS = {"X-Api-Key": "fs_sk_•••••••••••"}

SUPPORTED = {
    "instagram.com", "tiktok.com", "pinterest.com",
    "x.com", "facebook.com",
    "rutube.ru",          # <- the entire change
}

def host_of(link):
    return urlparse(link).netloc.removeprefix("www.").lower()

def save(link, path):
    host = host_of(link)
    if host not in SUPPORTED:
        raise ValueError("unsupported host: " + host)

    meta = requests.get(FETCH, params={"url": link},
                        headers=HEADERS, timeout=90).json()
    if not meta.get("ok"):
        raise RuntimeError(meta.get("detail", "fetch failed"))

    # Stream to disk — a 40-minute upload will not fit comfortably in memory.
    with requests.get(meta["download_url"], stream=True, timeout=300) as r:
        r.raise_for_status()
        with open(path, "wb") as f:
            for chunk in r.iter_content(1 << 16):
                f.write(chunk)
    return meta

save("https://rutube.ru/video/d1c0a7f0b4e94a2c8f5b6d3e1a7c9b02/", "clip.mp4")

Or, if you just want the file locally:

shell · curl + jq
URL=$(curl -s -G "https://api.fastsaver.io/v1/fetch" \
  --data-urlencode "url=https://rutube.ru/video/d1c0a7f0b4e94a2c8f5b6d3e1a7c9b02/" \
  -H "X-Api-Key: $FASTSAVER_KEY" | jq -r '.download_url')

curl -L -o clip.mp4 "$URL"

curl -G --data-urlencode does the escaping for you — the most common cause of a confusing empty result, gone.

honesty

what RuTube will not give you

  • Public videos only. Login walls, age gates, paid content and rights-holder blocks return an error. No parameter unlocks them.
  • No dimensions. width and height come back null on every RuTube video and are absent from an image response. Neither shape tells you the pixel size; if your UI needs it, read it off the downloaded file.
  • The download URL expires. Resolve and transfer in the same job. Re-resolving costs another 3 credits and still beats a dead link.
  • A failure is not free. Private, deleted or malformed links charge 0.1 credits each — 1/30th of a successful resolve, but budget for it if you replay a queue of links that never worked.
  • Files are large. RuTube hosts long-form video; forty minutes at 1080p runs to hundreds of megabytes. Stream to storage, set generous timeouts.
  • Throughput is capped per plan — 10 requests per minute on Free, 900 on Mega. On RuTube you will usually saturate your own transfer workers long before you see a 429.
  • Platforms move. When RuTube changes its player, we ship the fix behind the same endpoint. Your integration stays put.

What you do with the file is your responsibility. A download is not a licence — respect the uploader's rights, the platform's terms and your local copyright law. Archiving for research and republishing someone's video are different acts.

faq

questions about the RuTube API

Do I need a Russian proxy or a VPN to download RuTube videos?

No — that is the part we take on. Reaching RuTube is a networking problem: where a request originates changes what the platform returns. Your worker can sit in Frankfurt or Ohio and get the same JSON.

Does this need a RuTube account, token or cookies?

No. The only credential is your own X-Api-Key header. No OAuth dance, no session to keep warm, nothing to renew when RuTube rotates something.

What does one RuTube video cost?

3 credits — twice what a TikTok, Pinterest, X or Facebook link costs, and the difference pays for the geo-sensitive fetch described above. On Pro that is roughly 33,000 videos a month; the free tier covers about 333. Failures are cheap but not free: every failed resolve — private video, dead link, malformed URL — charges a flat 0.1 credits, so a retry loop over links that will never work still bills you. Checking /balance is free.

How long is the download_url usable?

Long enough to download, not long enough to store. Treat it as a one-shot handle: resolve, transfer, discard. Need the video next week? Call /fetch again — cheaper than debugging a link that died in your database.

Can I download private, restricted or paid RuTube videos?

No. The API resolves what an anonymous visitor can watch. Anything behind a login, an age gate, a purchase or a rights-holder block returns an error, and no parameter changes that.

Resolve a RuTube link right now

Paste a rutube.ru URL into the playground and look at the JSON before you commit to anything.