saveapi.org
Documentation

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[].

STEP 01
Create a key
Sign up and copy sk_live_… from the dashboard. The free plan starts with 1,000 credits.
STEP 02
Send one link
Call /v1/download with any supported URL. Detection is automatic.
STEP 03
Read medias[]
Take the first entry for the best quality, or filter by ext and size_mb.
First request
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.

Python 3.9+pip install saveapiPyPIGitHub
JavaScript / TypeScriptnpm install saveapinpmGitHub
Python
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, ...
JavaScript
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.

Core API
Instagram post or reel1.5 credits
Instagram stories5 credits
Instagram profile3 credits
TikTok, Facebook, X (Twitter), Pinterest, Likee1.5 credits
Snapchat, Threads, VK, 9GAG, Twitch1.5 credits
RuTube3 credits
Twitch VOD3 credits
Failed resolve0.1 credits
YouTube API
Video info2 credits
Download link10 credits
Audio link (mp3, m4a)10 credits
Failed resolve0.1 credits
Account
Link detectionfree
Platform listfree
Balance / usage checkfree

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.

StatusCodeMeaning
400INVALID_URLThe url parameter is missing, malformed, or not a valid post link.
400UNSUPPORTED_PLATFORMThe link does not belong to any platform we support. Call /v1/detect first.
401MISSING_API_KEYNo key was sent. Use Authorization: Bearer, or the X-API-Key header.
401INVALID_API_KEYThe key is malformed or unknown.
403KEY_REVOKEDThis key was revoked in the dashboard. Create a new one.
403KEY_EXPIREDThis key had an expiry date and that date has passed.
403ACCOUNT_SUSPENDEDThe account that owns this key is suspended. Contact support.
403IP_NOT_ALLOWEDThe key has an IP allowlist and this request came from another address.
403PLATFORM_NOT_ALLOWEDYour plan does not include this platform.
403PRIVATE_CONTENTThe post is private. Costs 0.1 credits.
400INVALID_FORMATThe quality you asked for is unknown or this video does not publish it. Read formats[] from /v1/youtube/info.
404MEDIA_NOT_FOUNDThe post is deleted, restricted, or holds no media. Costs 0.1 credits.
410LINK_EXPIREDThe download link from /v1/youtube/create has expired. Call it again for a fresh one.
429RATE_LIMITEDToo many requests this minute. Check the Retry-After header.
429QUOTA_EXCEEDEDYour credit balance is empty. Credits are bought in packs, not reset monthly — the balance is in X-Credits-Remaining on every response.
502UPSTREAM_ERRORThe source platform failed to respond. Not billed. Safe to retry.
504UPSTREAM_TIMEOUTThe source took too long to respond. Not billed. Safe to retry.
500INTERNAL_ERRORSomething broke on our side. Not billed.
JSON
{
  "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.

GET

/v1/download

from 1.5 credits
description

Resolves a link into direct media URLs plus author and caption metadata.

Query parameters
urlrequiredstring
The public link to resolve. Must be URL-encoded.
limit_mboptionalnumber
Cap the returned media size. Only used by platforms that offer variants.
full_metaoptionalboolean
Instagram: also return likes, comments, location, original audio and verified badge (adds ~0.3 s). Other platforms already include full meta; ignored there.
code examples
cURL
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"
response example
200 · application/json
{
  "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 }
}
API playground
Test the API directly from your browser — real requests, real credits.
GET https://api.saveapi.org/v1/download?url=https%3A%2F%2Fwww.tiktok.com%2F%40zachking%2Fvideo%2F6768504823336815877
Paste your API key in the bar above to send this requestcosts from 1.5 credits
generated code
cURL
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"
REFERENCE

Authentication

Every request carries your secret key in the Authorization header. Keys never expire, but you can revoke and rotate them at any time.

HTTP
Authorization: Bearer sk_live_xxxxxxxxxxxx

X-API-Key: sk_live_… is accepted as well, for clients that cannot set an Authorization header.

Keep the key server-side. Anyone who has it can spend your credits. If it leaks, revoke it in the dashboard and create a new one — revocation is instant.
REFERENCE

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.

JSON
{
  "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:

JSON
"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.

HTTP
X-Plan: pro
X-Credits-Remaining: 99988
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
REFERENCE

Rate 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.

PlanLimit
Free10 / min
Pro60 / min
Ultra250 / min
Mega900 / min

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
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
REFERENCE

Credits & 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.

EndpointCost
GET /v1/downloadfrom 1.5 credits
GET /v1/{platform}from 1.5 credits
GET /v1/youtube/info2 credits
GET /v1/youtube/create10 credits
GET /v1/instagram/profile3 credits
GET /v1/detectfree
GET /v1/platformsfree
GET /v1/mefree
GET /v1/usagefree

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.

PlanPriceCreditsRateValid
Pro$9100,00060 / min30 days
Ultra$39500,000250 / min30 days
Mega$991,500,000900 / min30 days

Full pricing and checkout →