---
title: "TikTok Downloader API — Videos & Photo Posts | FastSaverAPI"
url: https://fastsaverapi.com/tiktok-downloader-api/
description: "REST API for TikTok: resolve a video, a still or a multi-slide photo post to direct file URLs — and the sound with it, track title, artist and cover included."
updated: 2026-08-10
api_base: https://api.fastsaver.io/v1
site_index: https://fastsaverapi.com/llms.txt
openapi: https://fastsaverapi.com/openapi.json
---

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

- **Endpoint**: GET /fetch

- **Credits**: 1.5 per TikTok link

- **Auth**: X-Api-Key header

- **Returns**: file URLs, metadata + the audio track

## 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 track — music_url and a music block on every response, albums included.
- Short share links — vm.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.

## 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



```
curl -G "https://api.fastsaver.io/v1/fetch" \
  --data-urlencode "url=https://www.tiktok.com/@handle/video/7362918273645102345" \
  -H "X-Api-Key: fs_sk_•••••••••••"
```



```
{
  "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.

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



```
{
  "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.

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

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



```
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:



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

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

## questions about the TikTok API

**Q: What does the TikTok downloader API return for one link?**

A: 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.

**Q: Do vm.tiktok.com and vt.tiktok.com share links work?**

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

**Q: Can the API download TikTok photo and slideshow posts?**

A: 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 .

**Q: Can I get the audio track from a TikTok post?**

A: 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.

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

A: 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.

**Q: What does one TikTok download cost?**

A: 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.

## Keep reading

- TikTok without the watermark — why local removal costs quality
- Instagram downloader API — reels, posts, stories
- All supported platforms — same endpoint, nine sources
- Endpoint reference — run the call in the playground

## Resolve your first TikTok link

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

Open the playground
Get a free API key
