TikTok API Approval
An unaudited TikTok app is not a broken app. It publishes normally and every post lands private, with no parameter that changes it. Covers the two stages of approval, the scopes each unlocks, domain verification for URL uploads, the creator_info call before every publish, and what gets rejected.
TikTok API approval has two stages, and almost everyone discovers the second one the hard way. Getting the Content Posting API added to your app takes a form. Getting your posts to be visible to anyone other than the creator takes an audit. In between there is a state where the integration works perfectly, returns success on every call, and publishes every post as private. This covers both stages, the scopes each unlocks, and the two requirements that fail audits most reliably.

Two stages, not one
| Stage | What it is | What it unlocks | Typical wait |
|---|---|---|---|
| Product access | Add the Content Posting API to your app in the developer portal. A form: what the app does, who uses it, links to your site and policies | The endpoints start responding. You can authenticate, query creator info, and publish | Days |
| Content audit | A review of your app's posting behaviour, submitted separately once the integration works | PUBLIC_TO_EVERYONE becomes a legal privacy level. Posts become visible | Weeks, and variable |
The critical thing about this split: stage one gives you a working integration and stage two gives you a useful one. Between them, everything succeeds and nothing is visible.
There is also a distinction between the Direct Post capability and Upload (sometimes called draft or inbox). Direct Post publishes straight to the creator's profile. Upload drops the video into the creator's TikTok inbox for them to finish and post themselves. Upload has a lower bar. If your product can tolerate the creator completing the post in the app, that path avoids a large part of this process, and it is worth deciding deliberately rather than by default.
Life without the audit
This is the section that exists because nothing else on the internet says it plainly. We went through this stage ourselves and watched the posts land private, so what follows is observed rather than inferred.

An unaudited app can publish. Every post lands as private, and nothing in the API tells you that is going to happen.
You call the init endpoint, upload the video, receive a publish id, poll the status, and get a completed publish. TikTok returns success at every step. The video appears on the creator's account, visible only to them.
The part that makes this genuinely hard to catch: creator_info still lists PUBLIC_TO_EVERYONE in privacy_level_options. The response looks exactly like an audited app's. You build your composer from a list of privacy levels that includes public, the creator picks public, you send public, the call succeeds, and the post is private anyway. There is no field, no warning and no error that reveals the state you are in.
The gate is on access to the audited surface, not on the option list. That distinction is why so many teams conclude their code is wrong.
Three consequences worth planning around.
Your integration tests pass and your product is broken. Every assertion about the API succeeds, including any assertion about the privacy level you requested. The only way to catch this is to open the account in the app, logged in as somebody who is not the creator, and look.
Customers will report it as your bug. From their side, they scheduled a post, your dashboard says published, and their audience saw nothing. There is no error to show them. Put the app's audit status in your own UI and say so explicitly, before they find out.
You cannot back-fill. Posts published while unaudited stay private. Passing the audit does not retroactively publish them. If you onboarded customers during that window, those posts need to be re-created.
Plan the sequence so that the audit lands before customer onboarding, not after. It is the single most expensive ordering mistake in this process.
Scopes and what each one buys
| Scope | What it allows | Needed for posting? |
|---|---|---|
user.info.basic | Open id, display name, avatar | Yes, effectively mandatory |
user.info.profile | Profile fields including bio and verification | Recommended |
user.info.stats | Follower and like counts | No |
video.publish | Direct Post: publish straight to the profile | Yes, for direct posting |
video.upload | Upload to the creator's inbox as an unfinished draft | Yes, for the draft flow |
video.list | Read the creator's published videos | No, but needed for history and verification |
video.insights | Per-video analytics | No |
comment.list, comment.list.manage | Read and manage comments | No |
Two notes that matter.
video.publish and video.upload are different products, not two names for the same thing. Requesting one does not grant the other, and a product that offers both flows needs both.
Requesting scopes you do not demonstrably use is a rejection reason. Ask for what your submitted demo actually shows. Adding comment.list because you might want it later reads as overreach, and it can sink the parts of the submission you needed.
Domain verification for URL uploads
There are two ways to get a video to TikTok: send the bytes yourself (FILE_UPLOAD), or give TikTok a URL to fetch (PULL_FROM_URL).
PULL_FROM_URL is the better option at any scale, and it is the one we run. You do not proxy the bytes, TikTok pulls directly from your storage, and the upload path gets much simpler. The cost is that you have to do the verification below, once, properly.
It also requires that TikTok knows the domain is yours. Verify it in the developer portal, by serving a signature file at a known path or by adding a DNS TXT record. Until you do, any pull request is refused with a message about URL ownership verification rules, which reads as though your URL is malformed rather than your domain being unverified.
Three practical details.
Verify every domain you will actually serve from. A CDN subdomain is a different domain. Signed URLs on a bucket domain need that bucket domain verified, not your marketing site.
The URL has to be publicly fetchable at the moment TikTok pulls. Short-lived signed URLs expire. Give them enough lifetime to survive queueing and transcoding, not just the moment you generated them.
Verification is per app, not per account. Do it once, at setup, and treat it as infrastructure rather than something per customer.
creator_info is not optional
Before publishing, you have to ask TikTok what this specific creator is allowed to do:
curl -X POST "https://open.tiktokapis.com/v2/post/publish/creator_info/query/" \ -H "Authorization: Bearer ${ACCESS_TOKEN}" \ -H "Content-Type: application/json; charset=UTF-8"
{ "data": { "creator_username": "someone", "creator_nickname": "Someone", "privacy_level_options": ["PUBLIC_TO_EVERYONE", "MUTUAL_FOLLOW_FRIENDS", "SELF_ONLY"], "comment_disabled": false, "duet_disabled": false, "stitch_disabled": true, "max_video_post_duration_sec": 600 }, "error": { "code": "ok" } }
Four things in that response change what your publish request may contain.
privacy_level_options is per creator, not global. A private account will not offer PUBLIC_TO_EVERYONE. Sending a value that is not in this list is rejected. Note the asymmetry with the previous section: this list reflects the creator's settings, not your app's audit status, so its presence is not evidence that a public post will actually be public.
comment_disabled, duet_disabled and stitch_disabled are the creator's settings. If stitch_disabled is true, you cannot enable stitching for that post. Your UI should reflect the creator's actual options rather than offering everything and failing.
max_video_post_duration_sec varies by creator. Not everyone gets 10 minutes.
Skipping this call is an audit failure. TikTok expects the publishing UI to be built from this response, and reviewers check that the creator's settings are respected. It is also a correctness requirement: without it you are guessing at values the platform will reject.
Call it when the user opens the composer, not once at connection time. These settings change.
bundle.social
The audit, the domain verification and the creator_info contract are already ours.
Audited and domain-verified on our side, so your posts are public from day one.
What gets rejected
TikTok reviews a recording of your product, the same as Meta. The list below is what the review criteria ask for, read against what the API actually requires. We do not publish the details of our own submission, so treat this as a checklist rather than a war story.
The demo does not show the full flow. Reviewers need to see the login, the consent screen, the composer built from creator_info, and the post appearing. A product tour without the authorisation step fails.
The creator's settings are not respected. If your composer offers "allow duet" for a creator whose duet_disabled is true, that is a visible contradiction of what the API told you.
No visible confirmation of what will be posted. TikTok expects the creator to see and approve the caption, the privacy level and the interaction settings before publishing. A one-click publish with no review screen does not pass.
Missing or unreachable policy pages. Privacy policy and terms have to load, and they have to describe what you do with the content.
Scope overreach. Requesting more than the demo shows.
The theme is the same as every other platform review: the reviewer has limited time and no context. Narrate the recording, caption what is happening on screen, and point your written justification at timestamps. Our own experience with Meta's App Review was that the most common rejection reason was not a technical gap at all, it was the reviewer not understanding what they were looking at. That lesson transfers directly, and it costs more here, because TikTok's turnaround is measured in weeks rather than days.
Spam risk, and rejections that are not your fault
Once you are live, a category of failures shows up that no amount of correct code prevents. TikTok's anti-spam system rejects publishes based on the creator's behaviour, not yours.
| Reason | Meaning | Retryable? |
|---|---|---|
spam_risk_too_many_posts | The creator hit a daily posting limit | No |
spam_risk_too_many_pending_share | Too many uploads queued and unpublished for this creator | No |
spam_risk_user_banned_from_posting | The creator is banned from posting | No |
spam_risk_text | The caption tripped content filters | No |
spam_risk | Generic risk flag | No |
auth_removed | The creator revoked the app | No, needs re-authorisation |
video_pull_failed, photo_pull_failed | TikTok could not fetch your URL | Yes |
internal | TikTok-side failure | Yes |
The whole spam_risk family is terminal. Retrying makes it worse, because more pending shares is itself one of the triggers. Surface these to the customer with the actual reason, because "publishing failed" for spam_risk_user_banned_from_posting sends them to your support queue for a problem only TikTok can resolve.
The full classification logic across platforms, including which TikTok error codes we treat as transient, is in social media API error handling.
The shorter path
Two approval stages with different criteria, a middle state where everything works and nothing is visible, domain verification per app, a mandatory pre-publish call whose response shapes your UI, and a review that judges a video recording. Weeks of calendar time before your first customer publishes anything, and none of it is engineering.
bundle.social's TikTok API is already audited and verified. Your users connect their account, you send a video and a caption, and the post is public because the audit is ours. No developer portal, no domain verification, no recording.
If you want the endpoint-level detail on publishing rather than the access process, see our TikTok content posting API page.
Frequently asked questions
How long does TikTok API approval take?
Product access is typically days. The content audit that makes posts public takes considerably longer and is variable. Plan the audit before customer onboarding, because posts published while unaudited stay private permanently.
Why are my TikTok API posts private?
Because the app has not passed the content audit. An unaudited app publishes successfully and every post lands as self-only, regardless of the privacy level you send. What makes it hard to diagnose is that creator_info still lists PUBLIC_TO_EVERYONE as an available option, so nothing in the API indicates the state you are in.
What is the difference between video.publish and video.upload?
video.publish posts directly to the creator's profile. video.upload places the video in the creator's inbox for them to finish and post in the app. They are separate products with separate approval requirements.
Do I need to verify my domain for the TikTok API?
Only if you use PULL_FROM_URL, where TikTok fetches the video from your storage. Verify every domain you serve from, including CDN subdomains. FILE_UPLOAD avoids the requirement but means proxying the bytes yourself.
Can I skip the creator_info call?
No. Privacy options, comment, duet and stitch settings and maximum duration are per creator, and sending values the creator does not allow is rejected. Skipping it is also a reliable audit failure, because reviewers check that the composer reflects the creator's actual settings.