TikTok Video Specs That Survive the Upload (and the Posting API)

Your export looks sharp in QuickTime and soft on TikTok. Every spec page you find publishes the same table of numbers and then stops, which leaves the actual work undone: the encode that hits those numbers, the UI chrome that covers a fifth of your frame, and the Content Posting API that returns HTTP 200 while publishing to an audience of nobody.
Quick answer: TikTok video specs are 1080x1920 at 9:16, H.264 video in an MP4 or MOV container, AAC audio at 48 kHz, up to 10 minutes long, under 500 MB from the mobile app or 4 GB from the web uploader. Export around 8 to 12 Mbps because TikTok re-encodes everything on ingest, so higher bitrates are bytes you upload and lose. You can hit that with a local FFmpeg command, or send the source file to FFmpeg Micro as one API call and skip installing an encoder, hosting it, or watching a render queue.
The numbers TikTok enforces, and the ones it ignores
TikTok accepts far more than it keeps. The platform transcodes every upload into its own ladder, which means container and codec are hard gates while resolution and bitrate are targets you aim at to survive the re-encode with as little damage as possible.
| Spec | Value |
|---|---|
| Resolution | 1080x1920 (9:16), 720x1280 minimum for decent results |
| Aspect ratio | 9:16 preferred, 1:1 and 16:9 accepted and letterboxed |
| Video codec | H.264 (High profile), HEVC accepted from newer devices |
| Container | MP4 or MOV |
| Audio | AAC-LC, 48 kHz, stereo, 128 to 192 kbps |
| Frame rate | 30 or 60 fps, constant |
| Bitrate | 8 to 12 Mbps for 1080x1920 |
| Length | Up to 10 minutes |
| File size | 500 MB from the mobile app, 4 GB from the web uploader |
The 500 MB versus 4 GB split is the one that breaks pipelines quietly. A 10-minute 1080p file at 10 Mbps is roughly 750 MB, which uploads fine at tiktok.com/upload or through TikTok Studio and gets rejected on a phone. If your automation ends in "a human opens the app and posts it," you're on the 500 MB budget, not the 4 GB one.
One FFmpeg command that hits the spec every time
A single reusable preset covers the whole table. It scales to 1080x1920, pads anything that isn't 9:16, locks frame rate and pixel format, and writes the moov atom to the front so the file starts playing before it finishes downloading.
ffmpeg -i input.mp4 \
-vf "scale=1080:1920:force_original_aspect_ratio=decrease,\
pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black,fps=30,format=yuv420p" \
-c:v libx264 -profile:v high -level 4.1 -preset slow \
-b:v 10M -maxrate 12M -bufsize 20M \
-c:a aac -b:a 192k -ar 48000 -ac 2 \
-movflags +faststart tiktok.mp4
Four of those flags do the unglamorous work. format=yuv420p forces 4:2:0 chroma, without which a ProRes or screen-capture source lands as 4:2:2 and gets rejected or mangled. -level 4.1 keeps the stream inside what older Android decoders handle. fps=30 converts variable frame rate footage from screen recorders and phones into constant frame rate, which is what stops audio from drifting a second late by minute eight. +faststart matters for preview playback, and it's the same flag family covered in MP4 Not Playing in Browser? Four Encoder Flags Fix It.
If your source is 16:9 and you'd rather fill the screen than show black bars, swap the pad for a center crop:
-vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,fps=30,format=yuv420p"
That throws away the left and right thirds of the frame, so it works for talking heads and fails for anything with action at the edges.
The safe zone is where most of the frame goes
TikTok's interface sits on top of your video, not beside it. Measured against a 1080x1920 canvas in mid-2026, the caption and button stack covers roughly 320 px at the bottom, about 108 px at the top for the status bar and tabs, 60 px on the left, and 120 px on the right where the like, comment, and share column lives. Anything you burn into those regions is decoration behind a button.
Those margins translate straight into filter arguments. For a hook line near the top:
ffmpeg -i tiktok.mp4 -vf "drawtext=\
fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:\
text='I stopped hand-cropping clips':fontcolor=white:fontsize=64:\
x=(w-text_w)/2:y=200:box=1:boxcolor=black@0.55:boxborderw=24" \
-c:a copy hooked.mp4
For burned-in captions, keep the baseline above the bottom chrome with MarginV:
ffmpeg -i tiktok.mp4 \
-vf "subtitles=captions.srt:force_style='Alignment=2,MarginV=340,Fontsize=20,Outline=2'" \
-c:a copy captioned.mp4
If those captions come out sitting too low anyway, the cause is almost always that libass measures MarginV in the subtitle script's coordinate space rather than in video pixels. Open the generated .ass file, read PlayResY, and scale your margin by 1920 / PlayResY.
What the Content Posting API adds on top of the specs
Posting through TikTok's Content Posting API introduces requirements the spec pages never mention, because they're app-level rather than file-level. The documented flow at developers.tiktok.com is: call the creator info endpoint, initialize a publish, hand over the video, then poll status with the returned publish_id.
curl -X POST 'https://open.tiktokapis.com/v2/post/publish/video/init/' \
-H "Authorization: Bearer $TIKTOK_ACCESS_TOKEN" \
-H 'Content-Type: application/json; charset=UTF-8' \
-d '{
"post_info": {
"title": "The encode preset I reuse for every clip",
"privacy_level": "SELF_ONLY",
"disable_comment": false,
"video_cover_timestamp_ms": 1000
},
"source_info": {
"source": "PULL_FROM_URL",
"video_url": "https://cdn.example.com/renders/tiktok.mp4"
}
}'
Two constraints decide whether that call does anything useful. First, PULL_FROM_URL means TikTok's servers fetch the file, so the URL has to be publicly reachable with no signed-header requirement, and the domain has to be verified in your developer app. An unverified domain returns url_ownership_unverified, not a network error, which sends people hunting through firewall rules for an hour. The alternative source, FILE_UPLOAD, has you declare the total byte size and chunk count up front and push the bytes yourself.
Second, and this is the one that wastes whole afternoons: an unaudited TikTok Content Posting API client is restricted to SELF_ONLY visibility. The init call returns HTTP 200 and a valid publish_id, the status endpoint reports the post completed, and the video is private. Your pipeline looks healthy and your audience sees nothing. Passing "privacy_level": "PUBLIC_TO_EVERYONE" before your app clears audit gets rejected or silently downgraded, so query /v2/post/publish/creator_info/query/ first and use only the values it returns in privacy_level_options. Treat public posting as something you unlock through review, not something you set in JSON.
Batching it for a repost pipeline
For a repost pipeline running dozens of clips a day, the scaling problem is not the FFmpeg command, it's where the bytes live while the command runs. Automation builders wiring this by hand in n8n (community thread 99963, and the community n8n-nodes-tiktok node from igabm) usually start by downloading the video into the workflow, encoding it, then pushing it to TikTok. That routes a 750 MB file through a process that was never meant to hold it, and the instance falls over, which is the same failure described in Google Drive Video Automation Fails in n8n. Pass a URL Instead..
Keep the bytes out of the orchestrator. Store the source somewhere with a public HTTPS URL, send the encode as one API call, take a webhook back with the output URL, then hand that URL straight to PULL_FROM_URL. FFmpeg Micro runs that middle step with no servers to run and no encoder to install, and the Listing Kit blueprint is the click-through version of the preset above: one upload becomes a 9:16 TikTok cut plus 1:1, 4:5, and 16:9 variants, which is exactly what you need when the same clip goes to TikTok, Instagram, and YouTube in the same run.
Batching also runs into TikTok's own rate limiting on publish calls per user. Queue posts with a delay between them rather than firing a parallel batch, or you'll collect spam-risk rejections on otherwise valid uploads.
Pitfalls that cost real re-renders
Most TikTok upload failures trace back to four or five repeat offenders, and none of them show up in a spec table.
- Uploading a 4K master. TikTok re-encodes down to its own ladder, so a 60 Mbps ProRes file uploads slowly and looks identical to a 10 Mbps H.264 file once ingested.
- iPhone HDR footage going gray and washed out after TikTok's transcode, which is a tone-mapping problem you fix before upload. FFmpeg HDR to SDR: Stop iPhone Uploads Coming Out Gray has the filter chain.
- Variable frame rate screen recordings drifting out of audio sync on long videos. The
fps=30filter in the preset above is the fix, not an optional nicety. - Burned-in captions landing under the description text and CTA button because nobody accounted for the 320 px bottom chrome.
- Treating an HTTP 200 from the publish endpoint as proof the video is live. Check the resulting post's visibility, not the status code.
An encode preset is also the wrong tool for some jobs. If your output needs per-scene layouts that a designer controls, with branded template slots and text that reflows, you want a template-editor service rather than a filter chain. And if you post three videos a month by hand, the API audit process costs more than it saves. The preset approach pays off when the same transform runs hundreds of times, the way it does in the YouTube Shorts and LinkedIn versions of this pipeline.
FAQ
What size should a TikTok video be in 2026?
A TikTok video should be 1080x1920 pixels at a 9:16 aspect ratio, encoded as H.264 in an MP4 file at 8 to 12 Mbps. File size must stay under 500 MB if you upload from the mobile app, or under 4 GB through the web uploader at tiktok.com/upload and TikTok Studio.
Does TikTok accept MOV files?
TikTok accepts both MP4 and MOV containers. MOV files from an iPhone or a ProRes export usually work, but they're much larger than an equivalent H.264 MP4, so re-encoding to MP4 first keeps you inside the 500 MB mobile cap and makes the upload finish faster.
Why does my TikTok video look blurry after uploading?
TikTok re-encodes every upload, and blur comes from that second encode compounding damage already in your file. The usual causes are a low source bitrate, an upscaled non-1080p source, variable frame rate footage, or HDR color that TikTok tone-maps badly. Exporting a clean 1080x1920 H.264 file at around 10 Mbps in yuv420p gives the re-encoder the best starting point. You can verify the encode didn't degrade the source with VMAF scores in FFmpeg.
Do I need approval to post publicly through the TikTok Content Posting API?
Yes. Until your TikTok developer app passes audit, the Content Posting API forces SELF_ONLY visibility on every post, even when the API returns HTTP 200 with a valid publish_id. Public posting becomes available after your app clears TikTok's review, and you should read allowed values from the creator info endpoint rather than hardcoding a privacy level.
How long can a TikTok video be?
TikTok allows uploads up to 10 minutes long. At 1080x1920 and 10 Mbps, a full 10-minute video lands near 750 MB, which exceeds the 500 MB mobile app limit, so long-form uploads have to go through the web uploader or drop to a lower bitrate.
Wire the encode into your pipeline once and every clip comes out at spec, whether it's headed for TikTok, Reels, or Shorts. Sign up free and run the preset as one API call against your next batch.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

Fixed music volume is a compromise. FFmpeg audio ducking isn't.
FFmpeg audio ducking with sidechaincompress: the working filtergraph, the input order and format rules that break it, and the same mix as one API call.

Check Your Encode Didn't Wreck the Video: VMAF Scores with FFmpeg
Run ffmpeg vmaf to score an encode before it ships: the input order everyone gets backwards, what 90 means for social, and a pass/fail gate for CI.

Fix "Output File Is Empty, Nothing Was Encoded" in FFmpeg
An ffmpeg output file empty result is usually a warning, not an error: FFmpeg exits 0 with nothing encoded. Every cause, plus the ffprobe guard to catch it.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free