PostWharf, the tool we make, takes this work off your side. One API call publishes to TikTok through an audited client, so the post goes out public, and a refusal lands on the post's delivery receipt as a plain sentence.
Where TikTok puts the error
Outside the file upload itself, every response from the API carries an error object with a code, a message and a log id. On success the code is ok, and any other value means the request did not go through. TikTok asks for all three when you contact its support team, so store them with your record of the attempt.
Three things about that object catch people out:
- A post can fail after the request succeeded. The init call answers
okwith apublish_idas soon as TikTok accepts the job. The file is checked afterwards, and the result is on/v2/post/publish/status/fetch/asstatus: FAILEDwith afail_reason, or on thepost.publish.failedwebhook asreason. - The HTTP status is not the whole answer. Query Creator Info returns
spam_risk_too_many_posts,spam_risk_user_banned_from_postingandreached_active_user_capwith status 200, which TikTok's own table marks as intentional. The direct post and photo init calls return the same three with 403, and the draft video init returns the ban code alone. Branch on the code string. - Two names are spelled two ways. The endpoint pages list
invalid_paramand the v2 error list hasinvalid_params, so handle both. The log id islog_idin the example responses andlogidin the field tables.
TikTok Content Posting API error codes, by cause
The codes come from TikTok's Content Posting API reference (Direct Post, Upload, Photo, Query Creator Info, Get Post Status and the Media Transfer Guide) and its v2 error list, all read on 10 November 2026. "Init" means one of the three calls that start a post: /v2/post/publish/video/init/ for a video posted directly, /v2/post/publish/inbox/video/init/ for a video sent to the creator's inbox as a draft, and /v2/post/publish/content/init/ for photos. "Retry as is" means sending the same request again with nothing changed.
Sign-in and permission errors
| Error code | Where it comes back | What it means | Fix | Retry as is? |
|---|---|---|---|---|
access_token_invalid | 401, on every endpoint in the reference | The access token is invalid, expired or missing from the request. | Refresh the token, then send the request again. | No, refresh first |
scope_not_authorized | 401, on every endpoint in the reference | The creator has not granted the scope the call needs: video.publish for a direct post, video.upload for a draft. | Ask the creator to authorize your app again with that scope. | No |
scope_permission_missed | 400, in the v2 error list | Some fields in the request need scopes the token does not carry. | Read the message for what is missing and request that scope. | No |
token_not_authorized_for_specified_publish_id | 400, on status fetch and cancel | The access token is not authorized for that publish_id. | Send the request with the token that started the post. | No |
auth_removed | fail_reason | The creator removed your app's access while the file was still uploading or downloading, so TikTok ended the post. | Nothing to send again. The creator has to authorize the app first. | No |
Caps and spam-risk refusals
| Error code | Where it comes back | What it means | Fix | Retry as is? |
|---|---|---|---|---|
rate_limit_exceeded | 429, on every endpoint in the reference | Too many requests on one user access token. The init calls allow 6 a minute, Query Creator Info 20 and status fetch 30. | Wait, then send fewer requests. | Yes, after a wait |
spam_risk_too_many_posts | 403 on a direct post or photo init, 200 on Query Creator Info, and a fail_reason | The creator has reached the daily cap for posts made through the API. | Wait for the 24 hours to pass, or post from the TikTok app, which is TikTok's own suggestion. | No, wait |
spam_risk_too_many_pending_share | 403 on a draft video init or a photo init | Too many uploads are waiting for the creator to finish them. TikTok allows at most 5 pending shares in any 24 hours. | Wait, and do not queue more than five unfinished drafts for one creator in 24 hours. | No, wait |
reached_active_user_cap | 403 on a direct post or photo init, 200 on Query Creator Info | Your app has reached its daily quota of creators publishing through it. | The quota belongs to the app, not to this creator. Wait for it to reset. | No, wait |
spam_risk_user_banned_from_posting | 403 on any init, 200 on Query Creator Info, and a fail_reason | TikTok has banned the creator from making new posts. | Nothing in the request fixes it, and TikTok says not to retry. | No |
spam_risk_text | fail_reason | TikTok judged the caption text risky or spammy and ended the post. | Do not send the same text again. | No |
spam_risk | fail_reason | TikTok judged the publishing request itself risky and ended it. | TikTok gives no fix and says not to retry. | No |
App audit, privacy level and URL ownership errors
| Error code | Where it comes back | What it means | Fix | Retry as is? |
|---|---|---|---|---|
unaudited_client_can_only_post_to_private_accounts | 403 on a direct post or photo init | Your app has not passed TikTok's audit, and an unaudited app can post only to a private account. | While you build, test with an account set to private. To post publicly, pass the audit. | No |
privacy_level_option_mismatch | 403 on a direct post or photo init | privacy_level is missing, or is not one of the options Query Creator Info returned for this creator. | Call Query Creator Info first and send one of its privacy_level_options. | No |
url_ownership_unverified | 403 on any init that uses PULL_FROM_URL, and on cancel | The media URL is on a domain or URL prefix your app has not verified. | Verify the domain or prefix in TikTok for Developers, or send a video with FILE_UPLOAD. | No |
app_version_check_failed | 400 on a photo init with MEDIA_UPLOAD | The creator's TikTok app is older than version 31.8, which photo drafts need. | The creator updates the TikTok app. | No |
Request and upload errors
TikTok's guide lists the answers to the upload PUT by HTTP status alone, with no code string, so those rows are listed by status.
| Error code | Where it comes back | What it means | Fix | Retry as is? |
|---|---|---|---|---|
invalid_param or invalid_params | 400, on any init | A field in the request is wrong, and the message says which. | Correct the field. | No |
invalid_file_upload | 400, in the v2 error list | The uploaded file does not meet the API's specifications. | Correct the file and upload it again. | No |
invalid_publish_id | 400, on status fetch and cancel | No post exists with that publish_id. | Use the id the init call returned. | No |
publish_not_cancellable | 400, on cancel | The post has already reached a final state. | Nothing to cancel. Read its status. | No |
| none | 400 on the upload PUT | The headers are malformed, or Content-Length does not match the bytes in the body. | Correct the headers for that chunk. | No |
| none | 403 on the upload PUT | The upload_url has expired. It lasts one hour. | Start over with a new init call. | No, new init |
| none | 404 on the upload PUT | TikTok has no upload task for that upload_url. | Use the whole URL the init call returned, query string included. | No |
| none | 416 on the upload PUT | Content-Range does not match how much TikTok has received. | Read the Content-Range header on TikTok's response, which reports the bytes it has, and send the chunk that follows. | No |
internal_error, or any 5xx | Every endpoint in the reference | A fault on TikTok's side or on the network. | Try again later. On an upload, send the same chunk again. | Yes |
Media check failures and failed pulls
All of these arrive as a fail_reason on the post's status, after the init call has succeeded.
| Fail reason | What it means | Fix | Retry as is? |
|---|---|---|---|
file_format_check_failed | The file is in a format TikTok does not take. | Send video as MP4, WebM or MOV (H.264, H.265, VP8 or VP9), and photos as WebP or JPEG. | No |
duration_check_failed | The video does not meet TikTok's duration limits. Every creator can post 3 minutes, and some can post 5 or 10. | Read max_video_post_duration_sec from Query Creator Info before you send. | No |
frame_rate_check_failed | The frame rate is outside 23 to 60 FPS. | Export the video again inside that range. | No |
picture_size_check_failed | A video is under 360 pixels or over 4,096 pixels in height or width, or a photo is over 1080p. | Export it again at an accepted size. | No |
video_pull_failed, photo_pull_failed | TikTok could not download the file, or the download did not finish within one hour. | Check that the URL is public and not bandwidth-limited. If it is, send it again. | Yes, if the URL is good |
internal | Part of TikTok's service was unavailable. TikTok calls this one retryable. | Send it again. | Yes |
publish_cancelled | You cancelled the download yourself with /v2/post/publish/cancel/. | Nothing to fix. | No |
TikTok's Media Transfer Guide also caps a video at 4 GB and a photo at 20 MB. It does not say which fail reason a larger file gets.
Which TikTok errors are safe to retry
A TikTok post moves through three stages: the init call, the transfer of the file (your PUT requests, or TikTok pulling it from your URL), and TikTok's own processing. The stage where it failed decides what a retry does.
- A refusal at init made nothing. No
publish_idcame back, so a second call cannot double the post. It will be refused again until the cause changes, which is why nearly every init code in the tables says no. - A failed chunk is retried by itself. A 5xx on the upload
PUTmeans sending that one chunk again. Chunks have to go up in order. - A
FAILEDstatus ends thatpublish_id. TikTok's wording is that the entire process has failed, so trying again means a new init call. Do that unchanged forinternaland for a failed pull from a URL you have checked. TikTok says not to retryauth_removed,spam_risk_user_banned_from_posting,spam_risk_textorspam_risk. - A slow post is not a failed post. TikTok guarantees no time limit. Processing averages under half a minute for a 512 MB file and more than two minutes for 4 GB, and moderation of a public post usually finishes within a minute but can take a few hours. The post id is held back until moderation approves it, so an empty
publicaly_available_post_id(TikTok's spelling) is not an error.
An init call that times out leaves you with no publish_id, and so with nothing to poll. Treat the result as unknown: look at the account, or at your webhook events, before sending the post again. The wider rule for unknown outcomes on any network is in our guide to publishing retry rules.
The three caps behind the spam-risk and user-cap codes
Three different limits sit behind these codes, and two of the names start the same way. spam_risk_too_many_posts counts the creator's posts through the API in the last 24 hours. spam_risk_too_many_pending_share counts drafts that are waiting in the creator's inbox to be finished in TikTok. reached_active_user_cap counts the creators publishing through your app in a day, so no change to one creator's post clears it.
TikTok's reference gives a number for only one of the three, the 5 pending drafts. For the daily post cap there is nothing to hard-code, but there is an early warning. A direct post has to call Query Creator Info first to get the creator's privacy options, and that call returns spam_risk_too_many_posts once the cap is reached. Read it there and you find out before the file is sent.
Posts stay private until the app is audited
TikTok's Direct Post reference says all content posted by an unaudited client is restricted to private viewing, and that the restriction lifts only after the app passes an audit against TikTok's terms of service. The code unaudited_client_can_only_post_to_private_accounts is the stricter half of the same rule: an unaudited app can post only to a private account, and the attempt is blocked at the init call. An integration under construction therefore needs a test account set to private, and a refusal with this code is about the app's standing, not about the post. How long to budget for the audit, next to the reviews other networks ask for, is in social API approval timelines.
privacy_level_option_mismatch sits beside it and is easier to fix. A public account can be sent PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS or SELF_ONLY. A private account can be sent FOLLOWER_OF_CREATOR, MUTUAL_FOLLOW_FRIENDS or SELF_ONLY. A hard-coded PUBLIC_TO_EVERYONE therefore fails for a private account. TikTok expects the app to show the creator the options from Query Creator Info and send the one they picked, and its reference says that occurrences of this error suggest the app is violating its product-use guidance.
Pulled media needs a verified URL
PULL_FROM_URL works only for a domain or URL prefix that your app has verified in TikTok for Developers. The rules are narrower than they look:
- Verifying a domain covers that domain and the subdomains under it. Verify
static.example.comandvideo.static.example.comis covered, whileexample.comis not. - Verifying a URL prefix covers only URLs that begin with exactly that prefix.
- The URL has to use https and must not redirect. A URL that answers with a 3xx counts as invalid.
- The file has to stay reachable for the whole download, which times out one hour after it starts.
Photos have no other route. A photo post accepts PULL_FROM_URL only, with up to 35 image URLs, so url_ownership_unverified on a photo post cannot be avoided by uploading the file. A video can go by FILE_UPLOAD.
What PostWharf shows when TikTok refuses a post
With PostWharf you send one post to the TikTok scheduling API and read the result on its delivery receipt, where each network the post went to has its own row. There is no audit for you to wait out. Publishing goes through an audited client, so a TikTok post goes out public instead of being held private.
Some refusals are stopped before TikTok sees them. A caption over 2,200 characters is refused when you create the post, and so is a set of files TikTok will not carry: more than 35 images, more than one video, or images and a video together. The 422 that comes back names the network and the limit that was passed.
When TikTok itself refuses a post, its row carries the reason as a sentence. A failure in transit is sent again with backoff. A rejection is left alone, because it would fail the same way on the third attempt. If the post reached your other networks and not TikTok, it is marked partial, not published. Published posts are checked again afterwards, so a TikTok post that is removed later stops reporting as live.
The same detail is on the API. GET /v1/posts/:id returns one delivery per channel with its status, attempts, live URL and error text, and a failure event carries an action field that names the next step. PostWharf's own API errors are a different and much shorter list, a JSON error field with an HTTP status from 401 to 503, set out in the API reference. For failures on any network, not TikTok alone, start with social media publishing API errors and fixes.
A workspace is one brand, with its own channels and API key, and the API is part of every plan.
Sources consulted: TikTok for Developers (developers.tiktok.com), all read on 10 November 2026: · Direct Post, Photo and Query Creator Info, each updated 27 October 2026 · Upload, Get Post Status, Media Transfer Guide, Error Handling and Get Started with Direct Post, each updated 4 August 2026 · Check them again before you hard-code a rule.
Common questions
Where does TikTok's API report a failed post?
In two places. A refused request carries the reason in the error code of its response, and a post that fails after the request was accepted shows a failed status with a fail reason when you fetch its publish status, or on the failed-publish webhook.
Which TikTok API errors are safe to retry?
A failed upload chunk can be sent again by itself, and a failed status with an internal error can be tried again with a new init call. A refusal at init is refused again until its cause changes, and TikTok says not to retry a removed authorisation or its spam-risk refusals.
Why do posts from my TikTok app stay private?
TikTok's Direct Post reference says all content posted by an unaudited client is restricted to private viewing until the app passes an audit against TikTok's terms of service. An unaudited app can also post only to a private account.
PostWharf is priced per workspace rather than per connected channel, and every plan carries unlimited channels. See per-brand pricing.