saveapi.org

Download social media videos in Python

One GET, one JSON shape, 1.5 credits per successful resolve. This guide covers the parts that are not obvious from the response alone: how mixed posts arrive, when there is a choice of rendition and when there is not, which errors are worth a second attempt, and how to get the bytes without loading the file into memory.

Updated

The first call

httpx over the standard library's http.client is worth it here for the timeout argument alone: a source that hangs should fail your request, not hold it. Thirty seconds is a sane ceiling — the gateway's own upstream budget is lower than that.

Python
import httpx

r = httpx.get(
    "https://api.saveapi.org/v1/download",
    params={"url": "https://www.instagram.com/reel/Cx1a2b3c/"},
    headers={"Authorization": "Bearer sk_live_xxxx"},
    timeout=30,
)
data = r.json()

Read medias[] rather than guessing

Every response carries the same array whether the post held one video or ten images. Mixed posts keep their types in post order, so a carousel with two photos and a clip gives you two image entries and one video entry. Branch on type and the same code handles every platform.

Python
for media in data["medias"]:
    if media["type"] == "video":
        print("video:", media["url"])
    elif media["type"] == "image":
        print("image:", media["url"])
    elif media["type"] == "audio":
        print("track:", media["url"])

Pick a rendition where there is a choice

Some sources publish several renditions, and those responses carry formats[] — each entry with a ready URL and its exact size in megabytes. There is no second call and no format parameter: filter the list client-side for the largest file that fits your destination's ceiling.

Python
best = None
for fmt in data.get("formats", []):
    if fmt["size_mb"] <= 45 and (best is None or fmt["size_mb"] > best["size_mb"]):
        best = fmt

url = best["url"] if best else data["medias"][0]["url"]

Retry only what a retry can fix

A timeout or an upstream error is worth one more attempt. A private post, a deleted post or an unsupported domain is not — the second call returns the same answer and, for the errors that are billed, spends a second credit. Split the codes into two sets and act on the set, never on the message text.

Python
RETRY = {"UPSTREAM_TIMEOUT", "UPSTREAM_ERROR"}

def download(link: str, attempts: int = 2) -> dict:
    for attempt in range(attempts):
        data = httpx.get(API, params={"url": link}, headers=HEAD, timeout=30).json()
        if data["success"]:
            return data
        if data["error"]["code"] not in RETRY:
            raise RuntimeError(data["error"]["message"])   # qayta urinish yordam bermaydi
    raise RuntimeError("source unavailable")

Watch the rate-limit headers

Every response carries the key's per-minute limit and what is left of it, and paid keys also carry the credits remaining in the pack. Read them from the response you already have instead of polling a balance endpoint on a timer.

Python
print(r.headers["X-RateLimit-Limit"])       # kalitning daqiqadagi chegarasi
print(r.headers["X-RateLimit-Remaining"])   # shu daqiqada qolgani
print(r.headers.get("X-Credits-Remaining")) # paketdagi qolgan kredit

Stream the file instead of loading it

The resolved URL points straight at the source CDN, so you fetch it directly and nothing passes through the API. Stream it to disk: a 4K reel in memory is the fastest way to get a container killed.

Python
with httpx.stream("GET", data["medias"][0]["url"]) as source:
    with open("reel.mp4", "wb") as out:
        for chunk in source.iter_bytes():
            out.write(chunk)

Questions before you build

Is there an official Python SDK?
No, and one endpoint does not really need one. The whole client is an httpx call with a Bearer header — a wrapper function of eight lines gives you typing and retries without a dependency to keep updated.
Does the file pass through the API?
No. You receive a URL that points at the source CDN and you fetch the bytes directly, which is why download speed depends on that CDN and not on us.
How do I know how much a call cost?
The response carries credits.spent and credits.remaining. A successful resolve is 1.5 credits — 5 for an Instagram story, 3 for Rutube. A private or missing post costs 0.1, and anything that failed on our side costs nothing.

Platform references used here

Other guides

Run this guide with your own key

Key in under a minute. 1,000 free credits, no card, no expiry.

Get a key and try itFull API reference