Back to Blog
TutorialMar 2026

How to Post to TikTok with Python: Complete API Guide 2026

By the end of this guide, you'll have a working Python script that uploads videos to TikTok programmatically. Whether you're building an automation pipeline, integrating TikTok into your application, or simply want to stop manually uploading content, this tutorial provides everything you need with clean, copy-paste-ready code. (If you're wondering why we use the official API instead of scraping, see our TikTok API vs scraping comparison.)

Python is the most popular language for automation, and TikTok is the fastest-growing social platform. Combining them opens up powerful possibilities: scheduled posting, bulk uploads, AI-generated content pipelines, and seamless integration with your existing workflows. Let's build something practical.

What You'll Build

A complete Python automation system that can:

  • Upload videos to TikTok with captions and hashtags
  • Schedule posts for optimal posting times
  • Handle errors and retries automatically
  • Post photo carousels and batch content
  • Check post status and monitor performance

No external SDK required—we'll use the standard requests library that every Python developer already knows.

Prerequisites

Before we start writing code, make sure you have:

  • Python 3.8+ installed on your machine
  • A Postqued account — Sign up at postqued.com (free tier available)
  • API key — Generate this from your Postqued dashboard
  • A TikTok video — Have an MP4 file ready for testing

Optional but recommended:

  • Your TikTok account connected to Postqued (required for actual posting)
  • Basic familiarity with REST APIs

Installation

We'll keep dependencies minimal. You only need the requests library:

pip install requests

That's it. No bloated SDK, no complex dependencies, no OAuth libraries to wrestle with. Just clean HTTP requests.

Step 1: Authentication with API Key

Postqued uses simple API key authentication. No OAuth flows, no token refreshes, no expiration headaches.

import requests
import os

# Configuration
API_BASE_URL = "https://api.postqued.com"
API_KEY = os.environ.get("POSTQUED_API_KEY", "your-api-key-here")

# Headers for all requests
headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}

def test_auth():
    """Test that your API key is working."""
    response = requests.get(
        f"{API_BASE_URL}/v1/platform-accounts",
        headers=headers
    )
    
    if response.status_code == 200:
        accounts = response.json().get("accounts", [])
        print(f"✓ Authentication successful! Found {len(accounts)} connected account(s)")
        for account in accounts:
            print(f"  - {account.get('username')} ({account.get('platform')})")
        return True
    else:
        print(f"✗ Authentication failed: {response.status_code}")
        print(response.text)
        return False

if __name__ == "__main__":
    test_auth()

Security tip: Never hardcode your API key. Use environment variables or a .env file (we'll cover this in best practices).

Step 2: Upload a Video File

TikTok posting requires a three-step upload process: get a presigned URL, upload your file, then confirm completion.

import os
import json
from pathlib import Path

def upload_video(file_path: str) -> dict:
    """
    Upload a video file to Postqued storage.
    
    Args:
        file_path: Path to the video file (MP4 or MOV)
    
    Returns:
        dict: Content metadata including contentId
    """
    file_path = Path(file_path)
    file_size = file_path.stat().st_size
    
    # Step 1: Request upload URL
    print(f"Step 1: Requesting upload URL for {file_path.name}...")
    
    upload_request = {
        "filename": file_path.name,
        "contentType": "video/mp4",
        "fileSize": file_size
    }
    
    response = requests.post(
        f"{API_BASE_URL}/v1/content/upload",
        headers=headers,
        json=upload_request
    )
    
    if response.status_code != 200:
        raise Exception(f"Failed to get upload URL: {response.text}")
    
    upload_data = response.json()
    content_id = upload_data["contentId"]
    upload_info = upload_data["upload"]
    
    print(f"  ✓ Got upload URL (content ID: {content_id})")
    
    # Step 2: Upload file to presigned URL
    print("Step 2: Uploading file...")
    
    with open(file_path, "rb") as f:
        upload_response = requests.put(
            upload_info["url"],
            data=f,
            headers=upload_info.get("headers", {})
        )
    
    if upload_response.status_code not in [200, 204]:
        raise Exception(f"Upload failed: {upload_response.text}")
    
    print("  ✓ File uploaded successfully")
    
    # Step 3: Confirm upload
    print("Step 3: Confirming upload...")
    
    # Get video dimensions and duration (simplified - in production use ffprobe)
    confirm_data = {
        "contentId": content_id,
        "key": upload_data.get("key", f"content/{content_id}/{file_path.name}"),
        "filename": file_path.name,
        "contentType": "video/mp4",
        "size": file_size,
        "width": 1080,  # Update with actual dimensions
        "height": 1920,
        "durationMs": 15000  # Update with actual duration in milliseconds
    }
    
    confirm_response = requests.post(
        f"{API_BASE_URL}/v1/content/upload/complete",
        headers=headers,
        json=confirm_data
    )
    
    if confirm_response.status_code != 200:
        raise Exception(f"Failed to confirm upload: {confirm_response.text}")
    
    print("  ✓ Upload confirmed")
    
    return {
        "contentId": content_id,
        "filename": file_path.name,
        "size": file_size
    }

# Example usage
if __name__ == "__main__":
    try:
        result = upload_video("my_video.mp4")
        print(f"\nUpload complete: {result}")
    except Exception as e:
        print(f"Error: {e}")

Step 3: Post to TikTok

Now that your video is uploaded, let's post it to TikTok with a caption, hashtags, and privacy settings.

import uuid
from datetime import datetime

def post_to_tiktok(
    content_id: str,
    account_id: str,
    caption: str,
    privacy_level: str = "PUBLIC_TO_EVERYONE",
    allow_duet: bool = True,
    allow_stitch: bool = True,
    allow_comments: bool = True,
    schedule_time: datetime = None
) -> dict:
    """
    Post content to TikTok.
    
    Args:
        content_id: The content ID from upload_video()
        account_id: Your TikTok account ID from Postqued
        caption: Post caption (can include hashtags)
        privacy_level: PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, 
                      FOLLOWER_OF_CREATOR, or SELF_ONLY
        allow_duet: Whether to allow duets
        allow_stitch: Whether to allow stitches
        allow_comments: Whether to allow comments
        schedule_time: Optional datetime to schedule for later (UTC)
    
    Returns:
        dict: Publish request details
    """
    
    # Generate unique idempotency key (prevents duplicate posts)
    idempotency_key = str(uuid.uuid4())
    
    # Build the publish request
    publish_request = {
        "contentIds": [content_id],
        "targets": [{
            "platform": "tiktok",
            "accountId": account_id,
            "intent": "draft",  # Use "draft" for manual approval, "publish" for direct
            "caption": caption,
            "dispatchAt": schedule_time.isoformat() + "Z" if schedule_time else None,
            "options": {
                "privacyLevel": privacy_level,
                "disableDuet": not allow_duet,
                "disableStitch": not allow_stitch,
                "disableComment": not allow_comments
            }
        }]
    }
    
    print(f"Posting to TikTok...")
    print(f"  Caption: {caption[:50]}...")
    print(f"  Privacy: {privacy_level}")
    
    response = requests.post(
        f"{API_BASE_URL}/v1/content/publish",
        headers={**headers, "Idempotency-Key": idempotency_key},
        json=publish_request
    )
    
    if response.status_code != 200:
        raise Exception(f"Failed to create post: {response.text}")
    
    result = response.json()
    print(f"  ✓ Post created (ID: {result.get('id')})")
    
    return result

# Example usage
if __name__ == "__main__":
    # First, get your account ID
    accounts_response = requests.get(
        f"{API_BASE_URL}/v1/platform-accounts?platform=tiktok",
        headers=headers
    )
    accounts = accounts_response.json().get("accounts", [])
    
    if not accounts:
        print("No TikTok accounts found. Connect one in the Postqued dashboard.")
    else:
        account_id = accounts[0]["id"]
        
        # Upload and post
        video = upload_video("my_video.mp4")
        post = post_to_tiktok(
            content_id=video["contentId"],
            account_id=account_id,
            caption="Automating TikTok with Python! 🐍 #python #automation #coding",
            privacy_level="PUBLIC_TO_EVERYONE",
            allow_duet=True,
            allow_stitch=True
        )
        
        print(f"\n🎉 Success! Post ID: {post.get('id')}")

Step 4: Check Post Status

TikTok processes posts asynchronously. Here's how to check if your post went live:

import time

def check_post_status(publish_id: str, max_retries: int = 30, delay: int = 5) -> dict:
    """
    Poll the status of a publish request until complete.
    
    Args:
        publish_id: The publish request ID
        max_retries: Maximum number of status checks
        delay: Seconds between checks
    
    Returns:
        dict: Final status information
    """
    print(f"Checking status for publish request: {publish_id}")
    
    for attempt in range(max_retries):
        response = requests.get(
            f"{API_BASE_URL}/v1/content/publish/{publish_id}",
            headers=headers
        )
        
        if response.status_code != 200:
            print(f"  Attempt {attempt + 1}: Error checking status")
            time.sleep(delay)
            continue
        
        status_data = response.json()
        status = status_data.get("status")
        
        print(f"  Attempt {attempt + 1}: Status = {status}")
        
        # Terminal states
        if status in ["completed", "sent", "published"]:
            print(f"  ✓ Post successful!")
            targets = status_data.get("targets", [])
            for target in targets:
                if target.get("status") == "sent":
                    print(f"    TikTok URL: {target.get('publishedUrl', 'N/A')}")
            return status_data
        
        elif status in ["failed", "partial_failed", "canceled"]:
            print(f"  ✗ Post failed: {status}")
            return status_data
        
        # Still processing, wait and retry
        time.sleep(delay)
    
    print("  ⚠ Timeout waiting for status")
    return status_data

# Example usage
if __name__ == "__main__":
    # After creating a post
    # final_status = check_post_status(post["id"])
    pass

Complete Working Example

Here's a complete, production-ready script you can copy and run:

#!/usr/bin/env python3
"""
TikTok Python Automation Script
Posts videos to TikTok using the Postqued API

Requirements:
    pip install requests

Environment Variables:
    POSTQUED_API_KEY: Your Postqued API key
    TIKTOK_ACCOUNT_ID: Your TikTok account ID (optional, will auto-detect)

Usage:
    python tiktok_poster.py path/to/video.mp4 "Your caption here"
"""

import os
import sys
import json
import uuid
import time
import requests
from pathlib import Path
from datetime import datetime
from typing import Optional, List

class TikTokPoster:
    """Python client for posting to TikTok via Postqued API."""
    
    API_BASE_URL = "https://api.postqued.com"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        self._account_id: Optional[str] = None
    
    def get_accounts(self, platform: str = "tiktok") -> List[dict]:
        """Get connected platform accounts."""
        response = requests.get(
            f"{self.API_BASE_URL}/v1/platform-accounts?platform={platform}",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json().get("accounts", [])
    
    @property
    def account_id(self) -> str:
        """Get default TikTok account ID."""
        if self._account_id is None:
            accounts = self.get_accounts("tiktok")
            if not accounts:
                raise ValueError("No TikTok accounts found. Connect one in Postqued dashboard.")
            self._account_id = accounts[0]["id"]
        return self._account_id
    
    def upload_video(self, file_path: str) -> dict:
        """Upload a video file to Postqued storage."""
        file_path = Path(file_path)
        
        if not file_path.exists():
            raise FileNotFoundError(f"Video file not found: {file_path}")
        
        file_size = file_path.stat().st_size
        print(f"📤 Uploading {file_path.name} ({file_size / 1024 / 1024:.1f} MB)...")
        
        # Get upload URL
        upload_request = {
            "filename": file_path.name,
            "contentType": "video/mp4",
            "fileSize": file_size
        }
        
        response = requests.post(
            f"{self.API_BASE_URL}/v1/content/upload",
            headers=self.headers,
            json=upload_request
        )
        response.raise_for_status()
        
        upload_data = response.json()
        content_id = upload_data["contentId"]
        
        # Upload file
        with open(file_path, "rb") as f:
            upload_response = requests.put(
                upload_data["upload"]["url"],
                data=f,
                headers=upload_data["upload"].get("headers", {})
            )
        
        if upload_response.status_code not in [200, 204]:
            raise Exception(f"Upload failed: {upload_response.text}")
        
        # Confirm upload
        confirm_data = {
            "contentId": content_id,
            "key": upload_data.get("key", ""),
            "filename": file_path.name,
            "contentType": "video/mp4",
            "size": file_size,
            "width": 1080,
            "height": 1920,
            "durationMs": 15000
        }
        
        requests.post(
            f"{self.API_BASE_URL}/v1/content/upload/complete",
            headers=self.headers,
            json=confirm_data
        ).raise_for_status()
        
        print(f"  ✓ Upload complete (ID: {content_id})")
        
        return {"contentId": content_id, "filename": file_path.name}
    
    def post_video(
        self,
        video_path: str,
        caption: str,
        privacy: str = "PUBLIC_TO_EVERYONE",
        schedule: Optional[datetime] = None,
        **options
    ) -> dict:
        """Upload and post a video to TikTok."""
        
        # Upload video
        video = self.upload_video(video_path)
        
        # Create post
        idempotency_key = str(uuid.uuid4())
        
        publish_request = {
            "contentIds": [video["contentId"]],
            "targets": [{
                "platform": "tiktok",
                "accountId": self.account_id,
                "intent": "draft",
                "caption": caption,
                "dispatchAt": schedule.isoformat() + "Z" if schedule else None,
                "options": {
                    "privacyLevel": privacy,
                    "disableDuet": not options.get("allow_duet", True),
                    "disableStitch": not options.get("allow_stitch", True),
                    "disableComment": not options.get("allow_comments", True)
                }
            }]
        }
        
        print(f"📝 Creating post...")
        print(f"   Caption: {caption[:60]}{'...' if len(caption) > 60 else ''}")
        
        response = requests.post(
            f"{self.API_BASE_URL}/v1/content/publish",
            headers={**self.headers, "Idempotency-Key": idempotency_key},
            json=publish_request
        )
        response.raise_for_status()
        
        result = response.json()
        print(f"  ✓ Post created (ID: {result['id']})")
        
        return result
    
    def wait_for_publish(self, publish_id: str, timeout: int = 300) -> dict:
        """Wait for a publish request to complete."""
        print(f"⏳ Waiting for publish to complete...")
        
        start_time = time.time()
        while time.time() - start_time < timeout:
            response = requests.get(
                f"{self.API_BASE_URL}/v1/content/publish/{publish_id}",
                headers=self.headers
            )
            response.raise_for_status()
            
            status_data = response.json()
            status = status_data.get("status")
            
            if status in ["completed", "sent", "published"]:
                print(f"  ✓ Published successfully!")
                targets = status_data.get("targets", [])
                for target in targets:
                    if target.get("publishedUrl"):
                        print(f"    URL: {target['publishedUrl']}")
                return status_data
            
            elif status in ["failed", "canceled"]:
                print(f"  ✗ Publish failed: {status}")
                raise Exception(f"Publish failed: {status_data}")
            
            time.sleep(5)
        
        raise TimeoutError("Publish timed out")

def main():
    """CLI entry point."""
    api_key = os.environ.get("POSTQUED_API_KEY")
    
    if not api_key:
        print("Error: Set POSTQUED_API_KEY environment variable")
        sys.exit(1)
    
    if len(sys.argv) < 3:
        print("Usage: python tiktok_poster.py <video.mp4> \"<caption>\"")
        sys.exit(1)
    
    video_path = sys.argv[1]
    caption = sys.argv[2]
    
    poster = TikTokPoster(api_key)
    
    try:
        result = poster.post_video(video_path, caption)
        poster.wait_for_publish(result["id"])
        print("\n🎉 Done! Check your TikTok inbox for the draft.")
    except Exception as e:
        print(f"\n❌ Error: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()

Save this as tiktok_poster.py and run:

export POSTQUED_API_KEY="your-api-key-here"
python tiktok_poster.py my_video.mp4 "My automated TikTok post! #python"

Advanced Examples

Post Photo Carousels

TikTok supports photo carousels (2-35 images). Here's how to post them:

def upload_image(file_path: str) -> dict:
    """Upload an image file using direct upload."""
    file_path = Path(file_path)
    
    with open(file_path, "rb") as f:
        response = requests.post(
            f"{API_BASE_URL}/v1/content/upload-image",
            headers={"Authorization": f"Bearer {API_KEY}"},
            files={"file": (file_path.name, f, "image/jpeg")}
        )
    
    response.raise_for_status()
    return response.json()

def post_carousel(image_paths: List[str], caption: str, **options) -> dict:
    """Post a photo carousel to TikTok."""
    
    # Upload all images
    print(f"Uploading {len(image_paths)} images...")
    content_ids = []
    for path in image_paths:
        result = upload_image(path)
        content_ids.append(result["contentId"])
        print(f"  ✓ {Path(path).name}")
    
    # Create carousel post
    idempotency_key = str(uuid.uuid4())
    
    publish_request = {
        "contentIds": content_ids,
        "targets": [{
            "platform": "tiktok",
            "accountId": options.get("account_id"),
            "intent": "draft",
            "caption": caption,
            "dispatchAt": None,
            "options": {
                "privacyLevel": options.get("privacy", "PUBLIC_TO_EVERYONE"),
                "autoAddMusic": options.get("auto_music", True)
            }
        }]
    }
    
    response = requests.post(
        f"{API_BASE_URL}/v1/content/publish",
        headers={**headers, "Idempotency-Key": idempotency_key},
        json=publish_request
    )
    
    return response.json()

# Usage
# post_carousel(["img1.jpg", "img2.jpg", "img3.jpg"], "My carousel! #photos")

Schedule Posts for Later

Schedule posts to go live at the optimal time:

from datetime import datetime, timedelta
import pytz

def schedule_post(
    video_path: str,
    caption: str,
    schedule_datetime: datetime,
    timezone: str = "UTC"
) -> dict:
    """
    Schedule a TikTok post for a specific time.
    
    Args:
        video_path: Path to video file
        caption: Post caption
        schedule_datetime: When to publish (localized datetime)
        timezone: Timezone name (e.g., "America/New_York")
    """
    # Convert to UTC
    tz = pytz.timezone(timezone)
    if schedule_datetime.tzinfo is None:
        schedule_datetime = tz.localize(schedule_datetime)
    
    utc_time = schedule_datetime.astimezone(pytz.UTC)
    
    # Ensure it's in the future
    if utc_time < datetime.now(pytz.UTC):
        raise ValueError("Schedule time must be in the future")
    
    poster = TikTokPoster(API_KEY)
    
    result = poster.post_video(
        video_path=video_path,
        caption=caption,
        schedule=utc_time
    )
    
    print(f"✓ Scheduled for {schedule_datetime.strftime('%Y-%m-%d %H:%M %Z')}")
    print(f"  (UTC: {utc_time.isoformat()})")
    
    return result

# Usage: Schedule for tomorrow at 2 PM EST
# tomorrow_2pm = datetime.now() + timedelta(days=1)
# tomorrow_2pm = tomorrow_2pm.replace(hour=14, minute=0, second=0)
# schedule_post("video.mp4", "Scheduled post!", tomorrow_2pm, "America/New_York")

Batch Upload Multiple Videos

Process and upload multiple videos efficiently:

from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

def batch_upload(
    video_directory: str,
    caption_template: str,
    max_workers: int = 3
) -> List[dict]:
    """
    Batch upload all videos from a directory.
    
    Args:
        video_directory: Directory containing video files
        caption_template: Template for captions (use {filename} placeholder)
        max_workers: Number of concurrent uploads
    """
    video_dir = Path(video_directory)
    video_files = list(video_dir.glob("*.mp4")) + list(video_dir.glob("*.mov"))
    
    poster = TikTokPoster(API_KEY)
    results = []
    
    def upload_one(video_path: Path) -> dict:
        try:
            caption = caption_template.format(filename=video_path.stem)
            result = poster.post_video(
                video_path=str(video_path),
                caption=caption
            )
            return {"file": video_path.name, "status": "success", "id": result["id"]}
        except Exception as e:
            return {"file": video_path.name, "status": "failed", "error": str(e)}
    
    print(f"Processing {len(video_files)} videos with {max_workers} workers...")
    
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {executor.submit(upload_one, vf): vf for vf in video_files}
        
        for future in as_completed(futures):
            result = future.result()
            results.append(result)
            status_icon = "✓" if result["status"] == "success" else "✗"
            print(f"  {status_icon} {result['file']}")
    
    # Summary
    success_count = sum(1 for r in results if r["status"] == "success")
    print(f"\nComplete: {success_count}/{len(results)} succeeded")
    
    return results

# Usage
# batch_upload(
#     "/path/to/videos",
#     "Video: {filename} #batch #automation"
# )

Handle Errors Gracefully

Production code needs robust error handling:

import logging
from functools import wraps
from time import sleep

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class TikTokAPIError(Exception):
    """Custom exception for TikTok API errors."""
    pass

def retry_on_error(max_retries=3, delay=2, exceptions=(requests.RequestException,)):
    """Decorator to retry API calls on failure."""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == max_retries - 1:
                        raise
                    logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying...")
                    sleep(delay * (attempt + 1))  # Exponential backoff
        return wrapper
    return decorator

class RobustTikTokPoster(TikTokPoster):
    """TikTok poster with retry logic and error handling."""
    
    @retry_on_error(max_retries=3)
    def upload_video(self, file_path: str) -> dict:
        return super().upload_video(file_path)
    
    @retry_on_error(max_retries=3)
    def post_video(self, **kwargs) -> dict:
        return super().post_video(**kwargs)
    
    def safe_post(self, video_path: str, caption: str, **options) -> Optional[dict]:
        """
        Safely post a video with full error handling.
        
        Returns:
            Publish result dict or None if failed
        """
        try:
            # Validate inputs
            if not Path(video_path).exists():
                logger.error(f"Video file not found: {video_path}")
                return None
            
            if len(caption) > 2200:
                logger.warning("Caption too long, truncating...")
                caption = caption[:2197] + "..."
            
            # Attempt post
            result = self.post_video(video_path, caption, **options)
            
            # Wait for completion with timeout
            final_status = self.wait_for_publish(result["id"], timeout=600)
            
            return final_status
            
        except requests.HTTPError as e:
            if e.response.status_code == 401:
                logger.error("Authentication failed. Check your API key.")
            elif e.response.status_code == 429:
                logger.error("Rate limit exceeded. Please wait and try again.")
            else:
                logger.error(f"HTTP error {e.response.status_code}: {e.response.text}")
        except Exception as e:
            logger.error(f"Unexpected error: {e}")
        
        return None

# Usage
# poster = RobustTikTokPoster(API_KEY)
# result = poster.safe_post("video.mp4", "My caption")

Best Practices

Environment Variables for API Keys

Never commit API keys to version control. Use environment variables:

import os
from dotenv import load_dotenv

# Load from .env file
load_dotenv()

API_KEY = os.environ.get("POSTQUED_API_KEY")
if not API_KEY:
    raise ValueError("POSTQUED_API_KEY environment variable not set")

# Optional: Account ID (auto-detected if not set)
TIKTOK_ACCOUNT_ID = os.environ.get("TIKTOK_ACCOUNT_ID")

Create a .env file (add to .gitignore):

POSTQUED_API_KEY=pq_your_api_key_here
TIKTOK_ACCOUNT_ID=optional_account_id

Retry Logic

Networks are unreliable. Always implement retries:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=4, max=10),
    reraise=True
)
def upload_with_retry(file_path):
    return upload_video(file_path)

Rate Limit Handling

Respect API limits to avoid being throttled:

import time
from collections import deque

class RateLimiter:
    """Simple rate limiter for API calls."""
    
    def __init__(self, max_calls: int, period: int):
        self.max_calls = max_calls
        self.period = period
        self.calls = deque()
    
    def wait_if_needed(self):
        now = time.time()
        
        # Remove old calls outside the time window
        while self.calls and self.calls[0] < now - self.period:
            self.calls.popleft()
        
        # Wait if at limit
        if len(self.calls) >= self.max_calls:
            sleep_time = self.calls[0] - (now - self.period)
            if sleep_time > 0:
                time.sleep(sleep_time)
        
        self.calls.append(now)

# Usage
# limiter = RateLimiter(max_calls=10, period=60)  # 10 calls per minute
# limiter.wait_if_needed()
# make_api_call()

Troubleshooting Common Issues

"No TikTok accounts found"

Cause: Your TikTok account isn't connected to Postqued.

Fix:

  1. Log into your Postqued dashboard
  2. Go to Accounts → Add Account → TikTok
  3. Complete the OAuth authorization flow
  4. Run your script again

"API key invalid" (401 error)

Cause: API key is missing, expired, or incorrect.

Fix:

  1. Check your environment variable: echo $POSTQUED_API_KEY
  2. Verify the key in your Postqued dashboard
  3. Regenerate if necessary (Settings → API Keys)
  4. Ensure it starts with pq_

"Video upload failed"

Common causes:

  • File too large (max 1GB for videos)
  • Unsupported format (use MP4 or MOV)
  • Video shorter than 3 seconds or longer than 10 minutes
  • Network interruption during upload

Fix:

# Check file before uploading
from pathlib import Path

def validate_video(file_path: str) -> bool:
    path = Path(file_path)
    
    if not path.exists():
        raise FileNotFoundError(f"File not found: {file_path}")
    
    if path.suffix.lower() not in ['.mp4', '.mov']:
        raise ValueError(f"Unsupported format: {path.suffix}")
    
    size_mb = path.stat().st_size / 1024 / 1024
    if size_mb > 1000:
        raise ValueError(f"File too large: {size_mb:.1f}MB (max 1GB)")
    
    return True

"Post stuck in processing"

What's happening: TikTok is reviewing the video. Normal for new accounts.

Fix:

  • Wait 5-10 minutes
  • Check that video meets TikTok's community guidelines
  • Verify your TikTok account is in good standing
  • Use check_post_status() to monitor progress

"Rate limit exceeded" (429 error)

Cause: Too many requests in a short time.

Fix:

  • Add delays between posts
  • Implement the rate limiter shown above
  • Current limits: 10 publishes per minute

"Idempotency conflict"

Cause: Same idempotency key used twice.

Fix: Ensure you're generating unique UUIDs for each request:

import uuid
idempotency_key = str(uuid.uuid4())  # Always unique

Next Steps and Resources

Now that you can post to TikTok with Python, here are ways to extend this:

Content Automation Pipelines

  • Generate videos with AI libraries (MoviePy, OpenCV)
  • Pull content from APIs and auto-post
  • Build scheduled posting systems with cron/APScheduler

Integration Ideas

  • Post when you publish a new blog post
  • Cross-post from YouTube or Instagram
  • Build a content calendar with Django/Flask

Advanced Features

  • Add video analytics tracking
  • Build a multi-account management system
  • Create a web dashboard for your team

Learn More

Keywords covered in this guide: python tiktok api, tiktok api python example, python tiktok automation, python tiktok sdk, post tiktok python

Ready to automate your TikTok workflow? Get your API key and start building with Python today.