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

getting a TikTok video without the watermark, and why editing it out fails

Watermark pixels are burned into the frames of the file TikTok hands you, so a TikTok video with no watermark is not something you edit into existence — it is a different render that already exists. This guide covers where the watermark comes from, what cropping, blurring and re-encoding each cost you, and why resolving the link with one API call is a different problem entirely.

Last updated

Endpoint
GET /fetch
Cost
1.5 credits per link
Output
the clean render — not a crop, not a re-encode
You need
an API key and any HTTP client

background

where the TikTok watermark actually comes from

An upload does not stay the file you sent. TikTok transcodes it into playback renditions and, along the way, produces a watermarked copy: the animated watermark plus the creator's handle, composited into the frames. That copy is what the app's save button hands you. It is deliberate — the watermark is how a clip carries its source into Reels, Shorts and every repost account in between.

And it moves. The watermark drifts on a slow loop, so its position depends on how long the clip runs, and a short video may only ever show it in one spot. That is why a fix tuned on one clip fails on the next, and why "just crop the bottom right" is advice from someone who tested exactly one video.

the usual routes

the five workarounds, and what each one costs

Everything that starts from the stamped file is damage control. Each route below is a real thing people ship; none of them gets the original pixels back, because the original pixels were never in the file they are editing. The honest accounting:

1. crop it out

Take a 1080×1920 clip. The watermark plus padding occupies a band around 150 pixels tall, and because it travels you must cut that band everywhere it appears, not just in your test frame. Top and bottom gone, you are at 1080×1620 — a sixth of the picture, and no longer 9:16, so a vertical player letterboxes it or crops the sides to fit. That is two re-framings of someone else's shot to hide a logo, and a caption burned near the edge goes with it.

Scaling the crop back up to 1920 does not rescue the framing either: you are interpolating 1620 rows into 1920, which is a blur applied to the whole frame to hide a logo in one corner.

2. blur or inpaint the region

Better on paper, worse in a feed. Inpainting is per-frame work, so cost scales with duration rather than with file size, and a moving target has to be tracked before it can be painted over. What you get is a rectangle of invented pixels that drifts around the frame — motion draws the eye, so viewers notice the patch faster than they noticed the logo. On a static background it can pass; over hair, water, crowds or a moving camera it smears, and the smear is what your thumbnail catches.

3. re-encode and hope

What TikTok serves is already a lossy encode of a file the phone encoded lossily; yours makes a third generation. At the same bitrate you lose detail the previous pass kept; at double, you faithfully preserve the artefacts and double your storage. Banding shows first on gradients, skin and night footage. And the point worth saying plainly: re-encoding does not touch the watermark at all. It is a cost with no corresponding benefit — people reach for it because it is the step that follows a crop or a blur, not because it removes anything.

4. paste it into a mirror site

Fine for one video on a phone. As a dependency: no contract, no status page, an ad interstitial, a shared queue, and sometimes a transcode to fixed quality on the way out — point 3 with extra steps, run by someone whose incentive is bandwidth, not fidelity. You also cannot tell from the output whether you were handed the clean render or a cropped one, which is a bad property for something in a pipeline.

5. call the app's private endpoints yourself

The one route that can reach the unstamped file, and the one that breaks worst. It works until the signing scheme changes, and the failure mode is nasty: instead of an error you often get the stamped file back, so the pipeline quietly ships watermarked video until a human notices in a published post. Add device fingerprints, region rules and a maintenance burden that lands on whoever is on call.

RouteWhat it actually doesWhat it costs you
CropCuts every band the watermark travels throughA sixth of the frame and the 9:16 aspect ratio
Blur / inpaintPaints invented pixels over a moving targetPer-frame compute, plus an artefact viewers spot first
Re-encodeAdds a third lossy generationDetail or storage — and the watermark survives it
Mirror siteHands the problem to someone else's serverNo contract, no status page, often a fixed-quality transcode
Private endpointsImpersonates the app to reach the clean renderBreaks on every signing change, and fails back to the stamped copy silently

the shortcut

the file you actually want already exists

Reframe it. You are not erasing something from an image; you are addressing a different object. The unstamped rendition is on TikTok's infrastructure — it has to be, since the stamp goes on a copy — and the job is finding which URL points at it.

That is a resolution problem, and resolution is the part that rots: link formats change, hosts rotate, signatures grow parameters. Behind /fetch that upkeep is ours; your side stays a URL and a header.

step by step

one request, one clean file

Send the link as a query parameter and read download_url out of the JSON. No flag, no mode, no post-processing — clean is the only thing this returns.

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/@studio/video/7419028374651029384" \
  -H "X-Api-Key: fs_sk_•••••••••••"
200 OK · response
{
  "ok": true,
  "id": "7419028374651029384",
  "source": "tiktok.com",
  "type": "video",
  "download_url": "https://v16-webapp-prime.tiktok.com/video/tos/...",
  "thumbnail_url": "https://p16-sign.tiktokcdn-us.com/tos-...",
  "width": 1080,
  "height": 1920,
  "duration": 19,
  "caption": "sunrise, second attempt"
}

Check width and height against what the app plays before trusting any downloader, this one included: matching dimensions prove nothing was cropped or scaled. In Node it is a function and a write — resolve, then pull the bytes in the same run, because the CDN link is signed and short-lived.

node.js · single link
import { writeFile } from 'node:fs/promises';

const KEY = process.env.FASTSAVER_KEY;

async function resolve(link) {
  const u = new URL('https://api.fastsaver.io/v1/fetch');
  u.searchParams.set('url', link);
  const res = await fetch(u, { headers: { 'X-Api-Key': KEY } });
  const data = await res.json();
  if (!data.ok) throw new Error(data.detail ?? 'could not resolve ' + link);
  return data;
}

const post = await resolve('https://vm.tiktok.com/ZMAvfLFYc/');

// A slideshow carries no top-level download_url — its slides are in post.items.
if (post.type === 'album') throw new Error('photo post — save each entry in post.items');

const ext = post.type === 'video' ? '.mp4' : '.jpg';
const bytes = await fetch(post.download_url).then((r) => r.arrayBuffer());
await writeFile(post.id + ext, Buffer.from(bytes));

The type check matters, and not only as a formality: a slideshow answers album and that payload has no top-level download_url at all — the slides sit in an items array — while a single-image post answers image and behaves exactly like the video branch. The endpoint reference documents both shapes field by field.

at volume

doing it for a list of links

At volume the constraint is your plan's requests per minute, not the platform. Cap concurrency yourself, back off on a 429, and keep failures as data so one dead link does not take the run down.

python · batch resolve
import asyncio, httpx

API = "https://api.fastsaver.io/v1/fetch"
HEAD = {"X-Api-Key": "fs_sk_•••••••••••"}
GATE = asyncio.Semaphore(8)          # keep this under your plan's rpm

async def resolve(client, link):
    for attempt in range(3):
        async with GATE:
            r = await client.get(API, params={"url": link}, headers=HEAD, timeout=60)
        if r.status_code == 429:      # rate limited, not rejected
            await asyncio.sleep(2 ** attempt)
            continue
        data = r.json()
        if data.get("ok"):
            return data
        detail = data.get("detail", "")
        if "Insufficient credits" in detail:   # a plain 400, not a 402 — stop the run
            raise SystemExit(detail)
        return {"ok": False, "link": link, "detail": detail}
    return {"ok": False, "link": link, "detail": "rate limited"}

async def main(links):
    async with httpx.AsyncClient() as client:
        return await asyncio.gather(*(resolve(client, l) for l in links))

links = open("links.txt").read().split()
results = asyncio.run(main(links))

good = [r for r in results if r["ok"]]
bad = len(results) - len(good)
print(len(good), "resolved,", bad, "failed")

# a resolve bills 1.5 credits; a failure bills a flat 0.1, not nothing
print(round(len(good) * 1.5 + bad * 0.1, 1), "credits spent")

That last line is deliberately not len(results) * 1.5. A failed resolve is cheap but not free: a private post, a deleted video or a malformed URL charges a flat 0.1 credits instead of the 1.5 a success costs. Negligible on one bad link; a real line item if you replay a list of a few thousand dead ones every night.

Running out of credits is the one failure worth aborting on, and it does not arrive as a 402 — there is no 402 here. You get a 400 whose detail reads Insufficient credits. Please top up to your account., so match the message, not the status. The guides index lists the whole error surface once.

Resolving is cheap and fast; downloading is neither. Keep them separate so a slow transfer never holds a slot in the resolve pool — and start transfers promptly, because a signed URL from an hour ago is probably dead.

honesty

what a clean TikTok render still does not solve

  • Public posts only. Private accounts, friends-only posts and anything behind a login are out of reach by design.
  • Deleted stays deleted. A removed or moderated post fails permanently — do not queue a retry.
  • The URL expires before the file does. Keep the bytes if you need them later, or keep the post ID and re-resolve.
  • Rate limits are per plan. 10 requests a minute on Free, up to 900 on Mega. A 429 is back-pressure: slow the worker, do not rotate keys.
  • TikTok will change something. That repair is ours, but a bad hour is possible; incident notes go to the Telegram channel.
  • Attribution is now your problem. A clean file transfers no right to use it. Credit the creator, get permission, or leave it alone.

faq

questions people actually search for

Can I remove the watermark from a TikTok file I already downloaded?

Not cleanly. The watermark is in the pixels, so every fix is an edit: cropping changes the framing, painting over it leaves a smear, re-encoding spends a generation of quality. The undamaged frames only exist on TikTok's side — resolve the link again instead.

Is the file the API returns re-encoded or resized?

Neither. Nothing is transcoded in the middle — you get a URL to a stored file, and the width and height fields report its dimensions before you download a byte. If they match what the app plays, no scaling happened.

Why do free TikTok watermark remover sites keep breaking?

Most wrap a single unofficial request path. When TikTok changes it — every few months — the site errors out, or silently falls back to the stamped copy. Several also re-encode to a fixed quality to cut their bandwidth bill.

How many TikTok links can I process per minute?

Your plan's rate limit decides, not the platform: 10 requests a minute on Free, up to 900 on Mega. Each link costs 1.5 credits, and GET /balance is free, so a worker can poll it to see what is left.

Does downloading without the watermark make the video mine to repost?

No. The watermark is attribution, not a licence, and removing it changes nothing about who owns the clip. Reposting still needs the creator's permission, and TikTok's terms still apply to you.

Resolve one link and compare it yourself

Run a TikTok URL through the playground, download both copies, and look at the frames side by side.