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.
Pythonimport 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.
Pythonfor 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.
Pythonbest = 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.
PythonRETRY = {"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.
Pythonprint(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 kreditStream 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.
Pythonwith 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.