How to Capture TikTok Videos with a Screenshot API
Use TikTok oEmbed and a browser screenshot API to turn a public video page or embed into a PNG, JPEG, or WebP image.
Short answer: A screenshot API captures the rendered TikTok page or embed; it does not download the original video file. For a public video, first request TikTok’s official oEmbed response, then render either the TikTok URL or an HTML page containing the returned embed in a real browser context. Wait for the embed to hydrate before saving the PNG, JPEG, or WebP result.
TikTok documents oEmbed as the programmatic way to convert a video URL into embed markup and metadata. The endpoint is https://www.tiktok.com/oembed?url=.... A browser-based renderer is required because the embed JavaScript must execute before the video card appears. See TikTok’s Embed Videos documentation.
What the workflow captures
The output is a screenshot of one of these rendered surfaces:
- The public TikTok video page.
- An HTML wrapper containing TikTok’s oEmbed HTML and
https://www.tiktok.com/embed.js.
It is a visual capture, not an MP4 download. Creator download settings still apply to downloading the video itself; a screenshot workflow does not bypass them.
Step-by-step implementation
1. Validate the URL
Accept only the public TikTok URLs your application supports, such as https://www.tiktok.com/@creator/video/VIDEO_ID. Normalize tracking parameters if they are not needed, reject non-HTTP schemes, and impose a maximum URL length.
2. Request oEmbed data
URL-encode the complete TikTok URL. A successful response includes embed HTML and video metadata. Treat a non-2xx response or an empty html field as an unavailable post.
3. Build a small wrapper page
Place the returned HTML in a document, load TikTok’s embed script asynchronously, and give the page enough width and height for the card. Escape or sanitize any data you insert outside the trusted oEmbed response.
4. Render in a browser
Use a screenshot service that accepts a URL or HTML and can wait for JavaScript. Configure a viewport, output format, and either a selector wait or a delay. Full-page capture is useful when the wrapper includes text below the video.
5. Store the binary result
Save the response bytes immediately if your provider returns a temporary URL. For example, ScreenshotOne documents generated screenshot URLs as temporary for up to four hours unless caching or external storage is configured.
Complete Node.js example with Playwright
This example performs the entire do-it-yourself flow locally: it calls oEmbed, writes a wrapper page, launches Chromium, waits for the embed, and saves a PNG. Install dependencies with npm install playwright and install a browser with npx playwright install chromium.
import fs from 'node:fs/promises';
import { chromium } from 'playwright';
const videoUrl = process.argv[2];
if (!videoUrl || !/^https:\/\/www\.tiktok\.com\/@[^/]+\/video\/\d+/.test(videoUrl)) {
throw new Error('Pass a public TikTok URL such as https://www.tiktok.com/@creator/video/123');
}
const oembedUrl = `https://www.tiktok.com/oembed?url=${encodeURIComponent(videoUrl)}`;
const oembedResponse = await fetch(oembedUrl, {
headers: { accept: 'application/json' }
});
if (!oembedResponse.ok) {
throw new Error(`TikTok oEmbed failed: HTTP ${oembedResponse.status}`);
}
const oembed = await oembedResponse.json();
if (!oembed.html) throw new Error('TikTok returned no embed HTML; the post may be private or removed.');
const html = `
${oembed.html}
`;
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 600, height: 900 }, deviceScaleFactor: 2 });
await page.setContent(html, { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => document.querySelector('blockquote.tiktok-embed iframe, iframe[src*="tiktok"]'), null, { timeout: 30000 });
await page.waitForTimeout(2500);
await page.screenshot({ path: 'tiktok.png', fullPage: true, type: 'png' });
await browser.close();
console.log('Saved tiktok.png');
Equivalent oEmbed requests
cURL
curl -G 'https://www.tiktok.com/oembed' \
--data-urlencode 'url=https://www.tiktok.com/@creator/video/VIDEO_ID' \
-H 'Accept: application/json'
Python
import requests
video_url = "https://www.tiktok.com/@creator/video/VIDEO_ID"
r = requests.get(
"https://www.tiktok.com/oembed",
params={"url": video_url},
headers={"Accept": "application/json"},
timeout=30,
)
r.raise_for_status()
data = r.json()
if not data.get("html"):
raise RuntimeError("No embed HTML returned; the post may be private or removed.")
print(data["html"])
Node.js
const videoUrl = 'https://www.tiktok.com/@creator/video/VIDEO_ID';
const q = new URLSearchParams({ url: videoUrl });
const res = await fetch(`https://www.tiktok.com/oembed?${q}`);
if (!res.ok) throw new Error(`oEmbed failed: ${res.status}`);
const data = await res.json();
console.log(data.html);
Using a hosted screenshot API
Hosted services remove the work of operating Chromium. Urlbox documents URL and HTML rendering, synchronous and asynchronous POST rendering, screenshots, PDFs, videos, and metadata extraction. Its documented render endpoint is https://api.urlbox.com/v1/render. ScreenshotOne documents a URL endpoint at https://api.screenshotone.com/take, configurable output formats, and full_page. Follow each provider’s current authentication and request schema rather than copying credentials into client-side code.
For either service, the sequence remains the same:
- Call TikTok oEmbed.
- Submit the TikTok URL or generated wrapper HTML to the renderer.
- Set a browser-sized viewport and a wait condition or delay.
- Request PNG, JPEG, or WebP.
- Persist the bytes or copy a temporary result to durable storage.
Rendering options that matter
| Option | When to use it | Typical choice |
|---|---|---|
| Viewport | Controls the card layout and responsive breakpoints. | 390×844 for mobile, 1080×1920 for a story-like portrait, or 600×900 for a compact embed. |
| Format | Balances quality and file size. | PNG for crisp text, JPEG for photographs, WebP for smaller modern assets. |
| Wait condition | Prevents a blank or partially hydrated card. | Wait for the TikTok iframe or embed selector, then add a short delay. |
| Full page | Includes attribution or text below the video. | Enable for a complete wrapper; disable for a fixed social-card image. |
| Selector capture | Removes surrounding page chrome. | Capture the embed container rather than the entire document. |
| Background | Controls transparent versus white output. | Use white unless your destination supports transparency. |
Or skip the browser setup
ScreenshotNeo is a website screenshot API with a browser renderer, so you can send the public TikTok URL directly. The API can return PNG, JPEG, WebP, or PDF and supports waits, selectors, custom CSS and JavaScript, device presets, full-page capture, and caching. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://www.tiktok.com/@creator/video/VIDEO_ID -o tiktok.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://www.tiktok.com/@creator/video/VIDEO_ID"}, timeout=90)
r.raise_for_status()
open("tiktok.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://www.tiktok.com/@creator/video/VIDEO_ID' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo failed: ${res.status}`);
await Bun.write('tiktok.webp', res);
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and each response reports its verdict in X-Page-Verdict and X-Billed headers. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
Private, removed, or inaccessible videos
oEmbed only helps with content TikTok makes publicly embeddable. A private, age-restricted, region-restricted, deleted, or removed post may return an error, incomplete HTML, or a page that never hydrates. TikTok states that when a video is removed from the app, its embedded form is no longer accessible. Record the original URL and the oEmbed status so your job can be retried or marked unavailable instead of producing a misleading blank image.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| HTTP 400/404 from oEmbed | Malformed, private, deleted, or unsupported URL. | Validate the canonical URL, confirm it opens publicly, and show an unavailable result when it does not. |
| Blank white screenshot | JavaScript has not run or the renderer captured too early. | Wait for the iframe/embed selector and add a bounded delay; do not rely only on DOMContentLoaded. |
| Only a loading placeholder | Third-party iframe hydration is slow or blocked. | Increase the wait timeout, verify outbound requests are allowed, and capture from a browser context. |
| Cookie dialog covers the card | The rendered page displayed a consent layer. | Use a consent-aware renderer, hide the selector after consent, or render the oEmbed wrapper instead of the full TikTok page. |
| Screenshot is cropped | Viewport is shorter than the embed or full-page capture is disabled. | Increase height or enable full-page/element capture. |
| Text is blurry | Low device scale factor or an aggressively compressed format. | Use a retina scale/device scale factor and PNG or high-quality WebP. |
| Intermittent timeouts | Third-party scripts or network conditions vary. | Set an overall timeout, retry a small number of times with backoff, and mark repeated failures separately from unavailable posts. |
| API key exposed | Screenshot requests are made from browser code. | Proxy through your server and store the key in a secret manager or environment variable. |
Performance, reliability, and cost
- Reuse oEmbed metadata. Cache the response keyed by canonical video URL, but refresh when a post can change or disappear.
- Use bounded waits. A selector wait plus a short delay is usually faster and more deterministic than a large fixed sleep.
- Limit concurrency. Queue jobs and apply provider rate limits; launching an unbounded browser per URL increases memory use and failures.
- Choose the smallest image. Match the viewport and output format to the destination instead of capturing a full desktop page for a thumbnail.
- Retry selectively. Retry network errors and renderer timeouts; do not retry a confirmed private or removed post indefinitely.
- Track verdicts. Store HTTP status, renderer status, capture duration, and whether the result was blank or complete.
- Account for retention. If a provider returns a temporary URL, download it into your own storage before it expires.
- Protect creator and platform requirements. Keep TikTok attribution in the rendered embed where applicable, follow TikTok Developer Services terms, and obtain consent when your client sends content materials to TikTok.
FAQ
Can an API return the TikTok video file?
No. This workflow renders a page or embed into an image. It does not download the original video.
Do I need oEmbed if I screenshot the TikTok URL directly?
Not always. Direct URL capture can work, but oEmbed gives you a stable embed surface and metadata and is useful when the full TikTok page has extra chrome or consent UI.
Why is my result different from the TikTok app?
The screenshot is produced by a web browser rendering the public web page or embed, whose layout, account state, viewport, and scripts can differ from the native app.
Can I capture a removed post?
No reliable screenshot can be produced when TikTok no longer exposes the post or its embed.
Which output format should I choose?
Use PNG for text and archival clarity, JPEG for photographic thumbnails, and WebP when smaller files are more important than universal legacy compatibility.


