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

TikTok downloader API for videos, photo posts and short links

The TikTok downloader API turns a post link into direct file URLs with one GET request: /fetch works out whether it is a video, a still or a multi-slide photo post, answers with JSON, and hands you the sound alongside the picture. Short vm.tiktok.com and vt.tiktok.com links resolve exactly as the share sheet copied them.

Last updated

Endpoint
GET /fetch
Credits
1.5 per TikTok link
Auth
X-Api-Key header
Returns
file URLs, metadata + the audio track

coverage

what the endpoint resolves

One parameter: the link. The API works out what sits behind it and answers with JSON — the video file, every still in a photo post, and the sound underneath either. Auth is the X-Api-Key header.

  • Videos — the clean render, at the uploaded dimensions.
  • Photo / slideshow posts — each image separately, full size.
  • The audio trackmusic_url and a music block on every response, albums included.
  • Short share linksvm.tiktok.com and vt.tiktok.com, followed to the real post.
  • Canonical web links/@handle/video/<id>, tracking query string or not.

The logo and the @handle are composited into the frames before the file leaves TikTok's encoder, so there is no overlay to strip — /fetch resolves the rendition that was never stamped. The watermark guide is where the crop, inpaint and re-encode arithmetic lives, if you want to know why the local routes cost you picture quality.

No app, no device emulation, no session to keep warm: if a logged-out browser can open the post, the API can resolve it.

usage

the request

A single GET with the link as a query parameter. URL-encode it: TikTok links carry ?is_from_webapp= and friends, and a raw ampersand ends up in your query string instead of theirs.

GET https://api.fastsaver.io/v1/fetch 1.5 credits
request
curl -G "https://api.fastsaver.io/v1/fetch" \
  --data-urlencode "url=https://www.tiktok.com/@handle/video/7362918273645102345" \
  -H "X-Api-Key: fs_sk_•••••••••••"
200 OK · response
{
  "ok": true,
  "id": "7362918273645102345",
  "source": "tiktok.com",
  "type": "video",
  "download_url": "https://v16-webapp-prime.tiktok.com/video/tos/...",
  "width": 1080,
  "height": 1920,
  "thumbnail_url": "https://p16-sign.tiktokcdn-us.com/tos-...",
  "duration": 27,
  "caption": "three takes, one landing",
  "music_url": "https://sf16-ies-music.tiktokcdn.com/obj/tos-alisg-ve-...",
  "music": {
    "id": "7362918019283746105",
    "title": "original sound",
    "author": "handle",
    "cover": "https://p16-amd-va.tiktokcdn.com/img/...",
    "duration": 27,
    "album": null,
    "original": true,
    "download_url": "https://sf16-ies-music.tiktokcdn.com/obj/tos-alisg-ve-..."
  }
}

id is the numeric post ID and a sound primary key if you cache results, though a post that resolves without one falls back to echoing the link you sent. source names the platform that answered, useful when one worker takes every kind of link. duration is seconds — the key is always present, but it is null on photo posts and albums. music_url and music are the sound, covered below. On failure you get ok: false and a detail string.

shapes

photo posts: type image, or type album with items

Slideshow posts are not short videos — they are stills with a track underneath, and TikTok stores them that way. type tells you which of three shapes arrived. video and image both carry a top-level download_url with width and height beside it. album carries an items array instead — one entry per slide — and no top-level download_url, width or height at all. Everything after that is identical across the three shapes, the sound included.

photo post · response
{
  "ok": true,
  "id": "7401882736450192345",
  "source": "tiktok.com",
  "type": "album",
  "items": [
    { "type": "image", "download_url": "https://p16-sign.tiktokcdn-us.com/...", "thumbnail_url": "https://p16-sign.tiktokcdn-us.com/...", "width": null, "height": null },
    { "type": "image", "download_url": "https://p16-sign.tiktokcdn-us.com/...", "thumbnail_url": "https://p16-sign.tiktokcdn-us.com/...", "width": null, "height": null }
  ],
  "thumbnail_url": "https://p16-sign.tiktokcdn-us.com/tos-...",
  "duration": null,
  "caption": "berlin, roll three",
  "music_url": "https://sf16-ies-music.tiktokcdn.com/obj/tos-alisg-ve-...",
  "music": {
    "id": "6982174432095817482",
    "title": "night bus",
    "author": "Mara Vail",
    "cover": "https://p16-amd-va.tiktokcdn.com/img/...",
    "duration": 31,
    "album": "Low Ceilings",
    "original": false,
    "download_url": "https://sf16-ies-music.tiktokcdn.com/obj/tos-alisg-ve-..."
  }
}

Each item repeats the still as its own thumbnail_url, so a gallery component that wants a poster field has one without a second pass. Item width and height are usually null for TikTok stills, so size the layout after you have the bytes rather than from the JSON, and the top-level duration is null here — a slideshow has no runtime of its own, only the track's. Branch on type before you reach for a file: code that reads download_url unconditionally throws on every album, code that only handles album misses the single-still posts that come back as image, and someone will paste both on day one.

audio

the music block, which only TikTok returns

Every TikTok response ships the sound as well as the picture, without a second call or a parameter to ask for it: music_url is a direct link to the audio file, and music is the metadata around it. Videos, single stills and albums all get it. No other platform on /fetch returns anything like it, so if your product is about sounds rather than clips, this is the field to build on.

  • music_url — the audio file itself, identical to music.download_url. When a post has no resolvable audio, it and the whole music object are null, so null-check before you index into it.
  • music.title and music.author — the sound as TikTok credits it: a licensed track's name and artist, or original sound and the creator's handle when they recorded it themselves.
  • music.cover — the sound's own artwork, which is not the post thumbnail_url.
  • music.duration — the track's length in seconds. It will not always match the post's duration.
  • music.album and music.original — the album name where a licensed track has one, and whether the sound started with this creator.
  • music.id — TikTok's ID for the sound. Two posts using the same sound share it, which makes it the key to group by if you are tracking what is trending.

The names degrade independently of the URL. On some posts the block arrives with download_url and duration filled in while title, author, cover and album come back null — the file is still there, only the credits are missing. Read them defensively, and do not key a cache on music.title.

code

resolve, then save

Two steps, always: resolve the link, then pull the bytes before the signature ages out. This handles all three shapes plus the track, and streams to disk rather than buffering the clip in memory.

python
import requests
from pathlib import Path

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

def resolve(link: str) -> dict:
    r = requests.get(API, params={"url": link}, headers=HEAD, timeout=60)
    data = r.json()
    if not data.get("ok"):
        raise RuntimeError(data.get("detail", "link could not be resolved"))
    return data

def save(url: str, dest: Path) -> None:
    with requests.get(url, stream=True, timeout=120) as r:
        r.raise_for_status()
        with dest.open("wb") as f:
            for chunk in r.iter_content(64 * 1024):
                f.write(chunk)

post = resolve("https://vm.tiktok.com/ZMAvfLFYc/")

if post["type"] == "album":
    for i, item in enumerate(post["items"]):
        save(item["download_url"], Path(post["id"] + "_" + str(i) + ".jpg"))
elif post["type"] == "image":
    save(post["download_url"], Path(post["id"] + ".jpg"))
else:
    save(post["download_url"], Path(post["id"] + ".mp4"))

# the sound comes with every shape, albums included
if post.get("music_url"):
    save(post["music_url"], Path(post["id"] + ".mp3"))

One-off from a terminal, with jq:

shell
curl -sG "https://api.fastsaver.io/v1/fetch" \
  --data-urlencode "url=https://vt.tiktok.com/ZSAvfLFYc/" \
  -H "X-Api-Key: fs_sk_•••••••••••" \
  | jq -r '.download_url' \
  | xargs curl -Lo clip.mp4

That shell one-liner assumes a video. For an album, jq -r '.items[].download_url' gives you the slide URLs one per line, and jq -r '.music_url' gives you the sound whatever the shape.

honesty

where TikTok links stop resolving

  • Public posts only. Private accounts, friends-only posts and anything behind a login return an error, never a file.
  • Region locks are real. A post restricted to a market we cannot reach fails with a reason — better an error than a substitute clip you ship to a user.
  • Deleted is deleted. Removed or moderated posts are not recoverable from a cache; the failure is permanent, so do not queue a retry.
  • Signed URLs go stale. TikTok CDN links are short-lived. If you need the file next week, store the file, not the URL.
  • Rate limits scale with the plan — ten requests a minute on Free, nine hundred on Mega. A 429 is back-pressure, not a broken link.
  • TikTok moves. Playback hosts and link formats change every few months. Those repairs land on our side; your request shape stays put.

What you may then do with a resolved file is a copyright question rather than an API question. Sort that out before you republish anything.

faq

questions about the TikTok API

What does the TikTok downloader API return for one link?

A JSON object with ok, the post id, a type of video, image or album, and the file URLs for that shape. Videos and single stills carry a top-level download_url; multi-slide posts carry an items array instead. Every shape also carries thumbnail_url, duration, caption and the post's audio as music_url plus a music object.

Do vm.tiktok.com and vt.tiktok.com share links work?

Yes. Both are followed server-side to the canonical /@handle/video/<id> post before anything resolves, so paste whatever the share sheet copied.

Can the API download TikTok photo and slideshow posts?

It can. A multi-slide post resolves with type set to album and an items array holding one entry per still, each with its own download_url. A photo post with a single still comes back as type image with a plain top-level download_url and no items.

Can I get the audio track from a TikTok post?

Yes, and you do not have to ask for it. Every TikTok response carries music_url — a direct link to the sound file — next to a music object with its title, author, cover art, album and duration. Albums and single stills get the track too, which is the point: a slideshow is images plus a sound, and the sound is the half most downloaders drop.

Why does a TikTok link return an error instead of a video?

Almost always because the post is not publicly reachable: deleted, taken down, set to private or friends-only, or restricted to a region. The response says so with ok: false and a detail string rather than handing you a placeholder file.

What does one TikTok download cost?

1.5 credits per resolved call, the same as Pinterest, X and Facebook. The free tier's 1,000 credits cover roughly 660 posts — enough to prototype before you pick a plan. A link that fails to resolve is billed at 0.1 credits rather than nothing, so a retry loop over dead posts still costs you something.

Resolve your first TikTok link

Paste a share link into the playground and read the JSON before you commit to an integration.