Docs
One base URL, bearer auth, JSON in every response. You can be in production in ten minutes.
base urlhttps://api.saveapi.org/v1
One GET per link. Send any public post URL to /v1/download and read medias[] — every entry is a direct file URL from the source's own CDN, in the same reply. No second call, no format parameter: where a source publishes several ready renditions they all arrive together in formats[].
curl -s -G "https://api.saveapi.org/v1/download" \
-H "Authorization: Bearer sk_live_xxxx" \
--data-urlencode "url=https://www.tiktok.com/@zachking/video/6768504823336815877"Official SDKs
Thin clients for Python and JavaScript/TypeScript: the same endpoints and response shape, typed, with errors raised as SaveAPIError carrying the stable code. They retry only what a retry can fix — UPSTREAM_TIMEOUT, UPSTREAM_ERROR, and RATE_LIMITED after Retry-After — and never a private, deleted or unsupported link.
from saveapi import SaveAPI, SaveAPIError
api = SaveAPI("sk_live_...") # or set SAVEAPI_KEY
try:
post = api.download("https://www.tiktok.com/@zachking/video/6768504823336815877")
print(post["medias"][0]["url"])
except SaveAPIError as error:
print(error.code) # PRIVATE_CONTENT, MEDIA_NOT_FOUND, ...import { SaveAPI, SaveAPIError } from "saveapi";
const api = new SaveAPI({ apiKey: "sk_live_..." }); // or SAVEAPI_KEY
try {
const post = await api.download("https://www.tiktok.com/@zachking/video/6768504823336815877");
console.log(post.medias[0].url);
} catch (error) {
if (error instanceof SaveAPIError) console.log(error.code);
}MIT-licensed, with a Telegram bot and file-download examples: github.com/akhroruz/saveapi-sdk
Credits per request
Every endpoint tells you what it costs before you call it. Downloads are billed per successful resolve; everything else is free.
A thousand credits costs $0.09 on Pro and $0.066 on Mega — a fraction of a cent per standard download.
The exact figure comes back on every call in credits.spent, and the balance in X-Credits-Remaining. No need to keep your own ledger.
Error codes
Every error answers with the same envelope. Branch on code, never on message — messages may be reworded, codes do not change.
{
"success": false,
"error": {
"code": "INVALID_URL",
"message": "The url must be a public http(s) link to a post"
}
}Endpoints
Pick an endpoint on the left. Every one is a plain GET with the key in the header — no polling, no job ids.
/v1/download
from 1.5 creditsResolves a link into direct media URLs plus author and caption metadata.
urlrequiredstringlimit_mboptionalnumberfull_metaoptionalbooleancurl -s -G "https://api.saveapi.org/v1/download" \
-H "Authorization: Bearer sk_live_xxxx" \
--data-urlencode "url=https://www.tiktok.com/@zachking/video/6768504823336815877"{
"success": true,
"platform": "tiktok",
"type": "video",
"source_url": "https://www.tiktok.com/@zachking/video/6768504823336815877",
"video_id": "6768504823336815877",
"medias": [
{
"type": "video",
"url": "https://v16m.tiktokcdn-us.com/...",
"ext": "mp4",
"quality": null,
"width": 0,
"height": 0,
"size_mb": 4.19,
"watermark": false
}
],
"music": {
"type": "audio",
"url": "https://v16-ies-music.tiktokcdn-us.com/...",
"ext": "mp3",
"quality": null,
"width": 0,
"height": 0,
"size_mb": null,
"watermark": null
},
"meta": {
"title": "They rejected my application to Hogwarts but I still found a way to be a wizard. 🧹#illusion #magic #harrypotter",
"text": "They rejected my application to Hogwarts but I still found a way to be a wizard. 🧹#illusion #magic #harrypotter",
"author": "Zach King",
"author_url": "https://www.tiktok.com/@zachking",
"thumbnail": "https://p19-common-sign.tiktokcdn-us.com/...",
"duration": 18,
"views": 2382320428,
"published_at": 1575915337,
"username": "zachking",
"author_avatar": "https://p16-common-sign.tiktokcdn-us.com/...",
"author_verified": null,
"likes": 63400000,
"comments": 296700,
"shares": 2400000,
"saves": 1900000,
"location": null,
"region": "US",
"music": {
"title": "original sound",
"author": "Zach King",
"duration": 18,
"cover": "https://p16-common-sign.tiktokcdn-us.com/...",
"original": true
}
},
"formats": [
{ "format": "nowm", "label": "No watermark", "type": "video", "size_mb": 4.19, "exact": true, "watermark": false, "url": "https://v16m.tiktokcdn-us.com/..." },
{ "format": "hd", "label": "HD, no watermark", "type": "video", "size_mb": 3.0, "exact": true, "watermark": false, "url": "https://v16-notes.tiktokcdn-us.com/..." },
{ "format": "wm", "label": "With watermark", "type": "video", "size_mb": 4.25, "exact": true, "watermark": true, "url": "https://v16m.tiktokcdn-us.com/..." },
{ "format": "mp3", "label": "Audio (mp3)", "type": "audio", "size_mb": null, "exact": false, "url": "https://v16-ies-music.tiktokcdn-us.com/..." }
],
"recommended_format": "nowm",
"credits": { "spent": 1.5, "remaining": 99988.5 }
}curl -s -G "https://api.saveapi.org/v1/download" \
-H "Authorization: Bearer sk_live_xxxx" \
--data-urlencode "url=https://www.tiktok.com/@zachking/video/6768504823336815877"Authentication
Every request carries your secret key in the Authorization header. Keys never expire, but you can revoke and rotate them at any time.
Authorization: Bearer sk_live_xxxxxxxxxxxxX-API-Key: sk_live_… is accepted as well, for clients that cannot set an Authorization header.
Response
Every successful call answers with the same shape. medias[] holds the direct files — always at least one, several for a carousel. meta carries the title, author, thumbnail and duration when the source publishes them, and credits tells you what the call cost and what is left.
{
"success": true,
"platform": "tiktok",
"type": "video",
"source_url": "https://www.tiktok.com/@zachking/video/6768504823336815877",
"video_id": "6768504823336815877",
"medias": [
{
"type": "video",
"url": "https://v16m.tiktokcdn-us.com/...",
"ext": "mp4",
"quality": null,
"width": 0,
"height": 0,
"size_mb": 4.19,
"watermark": false
}
],
"music": {
"type": "audio",
"url": "https://v16-ies-music.tiktokcdn-us.com/...",
"ext": "mp3",
"quality": null,
"width": 0,
"height": 0,
"size_mb": null,
"watermark": null
},
"meta": {
"title": "They rejected my application to Hogwarts but I still found a way to be a wizard. 🧹#illusion #magic #harrypotter",
"text": "They rejected my application to Hogwarts but I still found a way to be a wizard. 🧹#illusion #magic #harrypotter",
"author": "Zach King",
"author_url": "https://www.tiktok.com/@zachking",
"thumbnail": "https://p19-common-sign.tiktokcdn-us.com/...",
"duration": 18,
"views": 2382320428,
"published_at": 1575915337,
"username": "zachking",
"author_avatar": "https://p16-common-sign.tiktokcdn-us.com/...",
"author_verified": null,
"likes": 63400000,
"comments": 296700,
"shares": 2400000,
"saves": 1900000,
"location": null,
"region": "US",
"music": {
"title": "original sound",
"author": "Zach King",
"duration": 18,
"cover": "https://p16-common-sign.tiktokcdn-us.com/...",
"original": true
}
},
"formats": [
{ "format": "nowm", "label": "No watermark", "type": "video", "size_mb": 4.19, "exact": true, "watermark": false, "url": "https://v16m.tiktokcdn-us.com/..." },
{ "format": "hd", "label": "HD, no watermark", "type": "video", "size_mb": 3.0, "exact": true, "watermark": false, "url": "https://v16-notes.tiktokcdn-us.com/..." },
{ "format": "wm", "label": "With watermark", "type": "video", "size_mb": 4.25, "exact": true, "watermark": true, "url": "https://v16m.tiktokcdn-us.com/..." },
{ "format": "mp3", "label": "Audio (mp3)", "type": "audio", "size_mb": null, "exact": false, "url": "https://v16-ies-music.tiktokcdn-us.com/..." }
],
"recommended_format": "nowm",
"credits": { "spent": 1.5, "remaining": 99988.5 }
}One key is not always there. formats[] appears only where the source itself publishes several ready renditions — VK, TikTok and Likee today — and every entry carries a direct URL and its exact size, so you filter for the one you want on your side. There is no second call and no format parameter. On every other platform the key is simply absent and medias[] is the answer:
"formats": [
{ "format": "mp4_144", "label": "144p", "type": "video", "size_mb": 0.76, "url": "https://..." },
{ "format": "mp4_480", "label": "480p", "type": "video", "size_mb": 7.9, "url": "https://..." },
{ "format": "mp4_1080", "label": "1080p", "type": "video", "size_mb": 36.37, "url": "https://..." }
],
"recommended_format": "mp4_1080"Response headers
Every response carries your plan, the remaining balance and the rate-limit window. Read X-Credits-Remaining to alert yourself before you run dry.
X-Plan: pro
X-Credits-Remaining: 99988
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59Rate limits
Limits are per key, per minute. Each key carries its own limit, so split traffic across keys when one service is hotter than another.
Over the limit you get 429 RATE_LIMITED with a Retry-After header — wait that many seconds and send again. Rejected calls are not billed.
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0Credits & billing
A successful download costs 1.5 credits — an Instagram story 5 and Rutube 3, because both cost us more to resolve. Every other endpoint is free. Credits are bought in packs and are valid for 30 days from purchase. The free 1,000 you get at signup never expire.
A link the source refuses — private, deleted, or with no media in it — costs 0.1 credits: the lookup ran and came back empty. Everything else costs nothing: bad links, unsupported sites, rate limits, source timeouts and our own errors.
Pricing
Billed per request: 1.5 credits for a download, 5 for an Instagram story, 3 for Rutube. A link the source refuses — private or deleted — costs 0.1. Paid credits are valid for 30 days from purchase.
Stuck or missing something?