TikTok
Configure TikTok in Outstand with your own API credentials (BYOK), including registering a developer app and OAuth setup.
TikTok is supported in Outstand through the Bring Your Own Key (BYOK) model. This allows you to use your own TikTok API credentials to connect your TikTok accounts and post content through Outstand's unified social media API.
On the TikTok dev portal register a new app. New apps have sandbox and production environments. You can use the sandbox environment to test your integration before going live.
Before you begin, ensure you have:
- A TikTok developer account
- A website or landing page for your application
- Understanding of OAuth 2.0 flows
- Access to your website's server/backend to set up callback routing (if using proxy solution)
Step 1: Create a TikTok Developer App
- Go to the TikTok Developer Portal
- Sign in or create a developer account
- Navigate to My Apps and click Create App
- Fill in the required information:
- App Name: Your application name (e.g., "My Video Scheduler" or "Outstand Video Scheduler")
- App Category: Select the appropriate category
- Website URL: Your application's website URL (must be HTTPS)
- Terms of Service URL: Your terms of service page (must be HTTPS)
- Privacy Policy URL: Your privacy policy page (must be HTTPS)
Important: TikTok requires brand consistency. Your app name, website URL, and redirect URI should all reference the same brand. See the Branding Requirements section below for details.
Step 2: Configure OAuth Settings
Understanding Redirect URIs
TikTok requires that your redirect URI matches your app's branding. Since Outstand uses a fixed callback URL (https://www.outstand.so/app/api/socials/tiktok/callback), you have two options:
Option A: Proxy Callback (Recommended for Brand Consistency)
Set up a proxy callback on your own domain that forwards requests to Outstand. This ensures TikTok sees your domain in the redirect URI, maintaining brand consistency.
Steps:
- In your TikTok app, add your redirect URI
- Set up a proxy endpoint on your server that forwards to Outstand's callback
- See Proxy Callback Implementation below for code examples
Option B: Use Outstand's Callback Directly
If brand consistency isn't critical for your use case:
- In your TikTok app, add the redirect URI:
https://www.outstand.so/app/api/socials/tiktok/callback - Ensure your app name and website URL mention Outstand or use generic branding
Required Scopes
Outstand requests the following scopes. You must enable all of them in your TikTok app:
Default Scopes:
user.info.basic- Access profile info (avatar and display name) - REQUIREDuser.info.profile- Read additional profile info (bio, profile link, verification status) - REQUIREDvideo.upload- Upload a draft to the creator's TikTok inbox (MEDIA_UPLOAD, the default post mode) - REQUIREDvideo.publish- Publish straight to the profile (DIRECT_POST) - REQUIREDuser.info.stats- Read profile engagement statistics (used for account insights)video.list- Read public videos on TikTok (for post metrics)
Note: Outstand requests all 6 scopes above. You must enable every one of them in your TikTok app configuration for the OAuth flow to work.
Step 3: Set Up Sandbox Testing
Before submitting for approval, test your integration using TikTok's sandbox environment:
- In your TikTok app, navigate to Sandbox
- Add a test user (your TikTok account)
- Test the OAuth flow and posting functionality
- Ensure all scopes work as expected
Step 4: Submit for Review
TikTok requires app review before you can use production APIs.
If you want to publish straight to a creator's profile (DIRECT_POST), your app must additionally pass TikTok's Content Posting API audit. Until it does, every Direct Post is forced to SELF_ONLY and only 5 distinct creators may publish through your app in a rolling 24 hours.
See TikTok Direct Post Audit for pre-filled application answers, a demo video script, the exact UI strings reviewers check, and an AI prompt to audit your implementation before you submit.
Common Rejection Reasons
TikTok may reject your app if:
-
Brand Inconsistency: App name, website, and redirect URI don't match
- Solution: Use the proxy callback approach (see below)
-
Missing Scope Demonstration: Demo video doesn't show all enabled scopes
- Solution: Update demo video to show each scope being used
-
Invalid Website: Website URL doesn't exist or isn't accessible
- Solution: Ensure your website is live and accessible via HTTPS
-
Incomplete Demo Video: Video doesn't show the complete user flow
- Solution: Record a comprehensive demo showing OAuth → posting → results
Proxy Callback Implementation
If you need to maintain brand consistency, set up a proxy callback on your domain that forwards requests to Outstand.
How It Works
- TikTok redirects to your callback:
https://yourdomain.com/api/socials/tiktok/callback?code=...&state=... - Your proxy endpoint forwards the request to Outstand:
https://www.outstand.so/app/api/socials/tiktok/callback - Outstand processes the OAuth flow and redirects back to your application
Implementation Examples
Next.js (App Router)
Create app/api/socials/tiktok/callback/route.ts:
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const url = new URL(request.url);
const outstandCallback = new URL('https://www.outstand.so/app/api/socials/tiktok/callback');
// Forward all query parameters
url.searchParams.forEach((value, key) => {
outstandCallback.searchParams.set(key, value);
});
// Forward cookies from the original request
const cookies = request.headers.get('Cookie') || '';
try {
// Forward the request to Outstand's callback
const response = await fetch(outstandCallback.toString(), {
method: 'GET',
headers: {
'Cookie': cookies,
},
redirect: 'manual', // Don't follow redirects automatically
});
// Get the redirect location from Outstand's response
const location = response.headers.get('Location');
if (location) {
// Redirect user to Outstand's final destination
return NextResponse.redirect(location);
}
// Fallback: redirect to Outstand callback directly
return NextResponse.redirect(outstandCallback.toString());
} catch (error) {
console.error('Proxy callback error:', error);
// Fallback: redirect to Outstand callback directly
return NextResponse.redirect(outstandCallback.toString());
}
}Next.js (Pages Router)
Create pages/api/socials/tiktok/callback.ts:
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { query } = req;
const outstandCallback = new URL('https://www.outstand.so/app/api/socials/tiktok/callback');
// Forward all query parameters
Object.entries(query).forEach(([key, value]) => {
if (typeof value === 'string') {
outstandCallback.searchParams.set(key, value);
}
});
// Forward cookies
const cookies = req.headers.cookie || '';
try {
const response = await fetch(outstandCallback.toString(), {
method: 'GET',
headers: {
'Cookie': cookies,
},
redirect: 'manual',
});
const location = response.headers.get('Location');
if (location) {
return res.redirect(302, location);
}
return res.redirect(302, outstandCallback.toString());
} catch (error) {
console.error('Proxy callback error:', error);
return res.redirect(302, outstandCallback.toString());
}
}Netlify Functions
Create netlify/functions/tiktok-callback.ts:
import type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions';
export const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
const queryParams = new URLSearchParams();
// Forward all query parameters
Object.entries(event.queryStringParameters || {}).forEach(([key, value]) => {
if (value) {
queryParams.set(key, value);
}
});
const outstandCallback = `https://www.outstand.so/app/api/socials/tiktok/callback?${queryParams.toString()}`;
// Forward cookies
const cookies = event.headers.cookie || '';
try {
const response = await fetch(outstandCallback, {
method: 'GET',
headers: {
'Cookie': cookies,
},
redirect: 'manual',
});
const location = response.headers.get('Location');
return {
statusCode: 302,
headers: {
'Location': location || outstandCallback,
},
};
} catch (error) {
console.error('Proxy callback error:', error);
return {
statusCode: 302,
headers: {
'Location': outstandCallback,
},
};
}
};Add to netlify.toml:
[[redirects]]
from = "/api/socials/tiktok/callback"
to = "/.netlify/functions/tiktok-callback"
status = 200
force = trueExpress.js
const express = require('express');
const router = express.Router();
router.get('/api/socials/tiktok/callback', async (req, res) => {
const outstandCallback = new URL('https://www.outstand.so/app/api/socials/tiktok/callback');
// Forward all query parameters
Object.entries(req.query).forEach(([key, value]) => {
outstandCallback.searchParams.set(key, value);
});
// Forward cookies
const cookies = req.headers.cookie || '';
try {
const response = await fetch(outstandCallback.toString(), {
method: 'GET',
headers: {
'Cookie': cookies,
},
redirect: 'manual',
});
const location = response.headers.get('Location');
if (location) {
return res.redirect(302, location);
}
return res.redirect(302, outstandCallback.toString());
} catch (error) {
console.error('Proxy callback error:', error);
return res.redirect(302, outstandCallback.toString());
}
});
module.exports = router;Vercel Serverless Function
Create api/socials/tiktok/callback.ts:
import type { VercelRequest, VercelResponse } from '@vercel/node';
export default async function handler(req: VercelRequest, res: VercelResponse) {
const { query } = req;
const outstandCallback = new URL('https://www.outstand.so/app/api/socials/tiktok/callback');
// Forward all query parameters
Object.entries(query).forEach(([key, value]) => {
if (typeof value === 'string') {
outstandCallback.searchParams.set(key, value);
}
});
// Forward cookies
const cookies = req.headers.cookie || '';
try {
const response = await fetch(outstandCallback.toString(), {
method: 'GET',
headers: {
'Cookie': cookies,
},
redirect: 'manual',
});
const location = response.headers.get('Location');
if (location) {
return res.redirect(302, location);
}
return res.redirect(302, outstandCallback.toString());
} catch (error) {
console.error('Proxy callback error:', error);
return res.redirect(302, outstandCallback.toString());
}
}Step 5: Obtain Your Credentials
Once your app is approved (or in sandbox mode):
- Navigate to your app in the TikTok Developer Portal
- Go to Basic Information
- You'll find:
- Client Key: Your OAuth client key (public identifier)
- Client Secret: Your OAuth client secret (keep this secure!)
Security Note: Your Client Secret is sensitive. Store it securely and never expose it in client-side code or public repositories.
Step 6: Add Credentials to Outstand
Once you have your Client Key and Client Secret, add them to Outstand:
Method 1: Using the API
curl -X POST https://api.outstand.so/v1/social-networks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"network": "tiktok",
"client_key": "YOUR_CLIENT_KEY_HERE",
"client_secret": "YOUR_CLIENT_SECRET_HERE"
}'Method 2: Using the Dashboard
- Log in to Outstand Dashboard
- Navigate to Settings → Social Networks
- Select TikTok from the dropdown
- Enter your Client Key
- Enter your Client Secret
- Click Add Social Network
Post Modes
TikTok's Content Posting API offers two ways to deliver a post. Choose with the postMode field in the tiktok block of Create a post.
MEDIA_UPLOAD (default) | DIRECT_POST | |
|---|---|---|
| What happens | The media is delivered to the creator's TikTok inbox as a draft. They write the caption, choose visibility and settings, and publish from the TikTok app. | The post is published straight to the creator's profile. |
| Endpoint | /v2/post/publish/inbox/video/init/ (video), /v2/post/publish/content/init/ (photo) | /v2/post/publish/video/init/ (video), /v2/post/publish/content/init/ (photo) |
| Scope | video.upload | video.publish |
privacyLevel | Not used - the creator picks visibility in the app | Required, and must come from the creator's privacy_level_options |
Other tiktok fields | Not used for video drafts; the caption is kept for photo drafts | All apply |
| Counts against the daily creator cap | No | Yes |
| Goes live on schedule | No - the creator must finish it in the TikTok app | Yes |
MEDIA_UPLOADposts have no public URL or analytics until the creator publishes them. The storedplatformPostIdis TikTok'spublish_idrather than a video ID,platformPostUrlisnull, and post analytics return an error explaining the post is still a draft. TikTok also expires publish records after about 24 hours, after which the draft can no longer be correlated back to the Outstand post.
Media Requirements
TikTok media is uploaded through the Content Posting API. See the official Media Transfer Guide and Direct Post reference.
Video
| Requirement | Specification |
|---|---|
| Formats | MP4 (H.264), MOV, WEBM |
| Maximum duration | Dynamic - read max_video_post_duration_sec from the Query Creator Info endpoint before posting |
| Upload chunking | Videos under 5 MB are uploaded whole; larger videos are chunked (each chunk 5-64 MB, final chunk up to 128 MB, max 1,000 chunks) |
| Transfer method | PULL_FROM_URL - Outstand gives TikTok the media URL and TikTok fetches it, so the URL must stay reachable until publishing completes |
| Upload URL validity | The issued upload_url is valid for 1 hour |
TikTok enforces the maximum video duration per creator - always fetch the current value from
max_video_post_duration_secrather than assuming a fixed number.
Photo Mode
| Requirement | Specification |
|---|---|
| Formats | WebP, JPEG |
| Number of photos | 1 to 35 images |
| Delivery | Provided as publicly accessible image URLs |
See the Photo Post reference.
Publishing Rate Limits
TikTok rate-limits the Content Posting API per user access token on a 1-minute sliding window. See the official rate limit documentation.
| Endpoint | Limit |
|---|---|
Post init (/v2/post/publish/...) | 6 requests per minute per user |
| Status / fetch endpoints | 30 requests per minute per user |
| Direct Post active creator cap | Rolling 24 hours, per API client - see below |
- Exceeding a limit returns HTTP 429 with a
rate_limit_exceedederror. - TikTok also applies per-creator daily posting limits that are enforced platform-side; these are not published as a fixed API number and are shared across all apps posting for that creator.
- Separately, TikTok caps how many distinct creators may publish through one API client in a rolling 24 hours. Unaudited clients are limited to 5; audited clients get a cap based on the usage estimates in their audit application. Only
DIRECT_POSTdraws on this cap -MEDIA_UPLOADdoes not. Exceeding it returnsreached_active_user_cap(see Publishing errors).
Comments and Replies
Comments and replies are not supported for TikTok.
As a result, the following are not available for TikTok accounts:
- Publish a comment (
POST /v1/posts/{id}/replies) - returns an error for TikTok accounts. - Get post replies/comments (
GET /v1/posts/{id}/replies) - returns an error for TikTok accounts. - First comment via the
containersfield - the reply container cannot be delivered to TikTok.
What TikTok does expose, and Outstand supports:
- Disabling comments at publish time. TikTok's Content Posting API accepts a per-post
disable_commenttoggle, so you can turn comments off on a post (or photo) when you publish it. - Comment counts in analytics. The read-only
comment_countmetric is included in post analytics and account insights.
Troubleshooting
OAuth Flow Fails
- Check redirect URI: Ensure it exactly matches what's configured in TikTok (including trailing slashes)
- Verify scopes: All 6 scopes must be enabled in your TikTok app
- Check sandbox: If in sandbox mode, ensure test user is added
App Rejected by TikTok
- Brand inconsistency: Use proxy callback solution to match redirect URI to your domain
- Missing scope demo: Update demo video to show all enabled scopes
- Invalid website: Ensure website URL is accessible and uses HTTPS
Scope Errors
- Missing scopes: Enable all 6 scopes in TikTok app configuration
- Scope mismatch: Ensure TikTok app scopes match what Outstand requests
Publishing errors
These are TikTok error codes returned when a post is published. Outstand surfaces them on the failed social account with guidance attached.
| Code | What it means | What to do |
|---|---|---|
reached_active_user_cap | The app has already published for as many distinct creators as TikTok allows in a rolling 24 hours. Unaudited clients get 5; audited clients get a cap set from their audit application. The cap belongs to the TikTok app, so every account sharing that app draws on the same pool. | Publish with postMode: "MEDIA_UPLOAD", which is not subject to the cap; or connect your own TikTok app credentials so you get your own cap; or wait for the 24-hour window to roll over. To raise the cap, get the app audited and then request an increase through TikTok's developer support form - the app must be in production, and increases are not guaranteed. See Direct Post Active Creator Cap. |
unaudited_client_can_only_post_to_private_accounts | The TikTok app has not passed the Content Posting API audit, so Direct Posts are forced to private. | Set privacyLevel to SELF_ONLY, use MEDIA_UPLOAD, or submit the app for audit. |
spam_risk_too_many_posts | The creator hit TikTok's per-account daily posting limit. | Reduce posting frequency for that creator and retry later. |
spam_risk_user_banned_from_posting | TikTok has blocked the creator from posting. | The creator needs to contact TikTok support - this cannot be resolved from Outstand. |
rate_limit_exceeded | More than 6 publish requests in a minute for that creator. | Retry after a minute. |
url_ownership_unverified | The media URL's domain is not verified for the TikTok app. | Verify the domain under URL Properties in the TikTok developer portal. |
privacy_level_option_mismatch | The chosen privacyLevel is not one the creator is allowed to use. | Re-fetch the creator's privacy_level_options and pick one of them. |
access_token_invalid | The connection expired or was revoked. | Reconnect the TikTok account. |
scope_not_authorized | The connection is missing video.publish (Direct Post) or video.upload (inbox drafts). | Reconnect the account and approve every requested scope. |