Problem: Posting to TikTok Takes Too Long
You want a consistent TikTok presence but manually creating and uploading memes every day is eating your time. You need a bot that generates AI memes and posts them automatically on a schedule.
You'll learn:
- Generate meme images using OpenAI's DALL-E 3 API
- Upload videos/images to TikTok via their Content Posting API
- Schedule posts with Python's
schedulelibrary
Time: 30 min | Level: Intermediate
Why This Works
TikTok's Content Posting API supports direct image and video uploads via OAuth 2.0. Combined with DALL-E 3 for image generation and Pillow for meme text overlay, you get a fully automated pipeline.
What you'll build:
- A meme generator that prompts DALL-E 3 and overlays text
- An uploader that authenticates with TikTok's API
- A scheduler that runs it daily
Prerequisites
pip install openai requests pillow schedule python-dotenv
Create a .env file:
OPENAI_API_KEY=sk-...
TIKTOK_CLIENT_KEY=your_client_key
TIKTOK_CLIENT_SECRET=your_client_secret
TIKTOK_ACCESS_TOKEN=your_access_token # See Step 1
Get TikTok credentials: Register at developers.tiktok.com, create an app, and enable Content Posting API access. You'll need a verified developer account.
Solution
Step 1: Authenticate with TikTok
TikTok uses OAuth 2.0. For a personal bot, generate a long-lived access token once using the authorization code flow.
# auth.py - Run this ONCE to get your access token
import requests
import os
from dotenv import load_dotenv
load_dotenv()
def get_access_token(auth_code: str) -> dict:
"""
Exchange auth code for access token.
Get auth_code by visiting the OAuth URL TikTok provides in your dev console.
"""
response = requests.post(
"https://open.tiktokapis.com/v2/oauth/token/",
headers={"Content-Type": "application/x-www-form-urlencoded"},
data={
"client_key": os.getenv("TIKTOK_CLIENT_KEY"),
"client_secret": os.getenv("TIKTOK_CLIENT_SECRET"),
"code": auth_code,
"grant_type": "authorization_code",
"redirect_uri": "https://your-redirect-uri.com/callback",
}
)
return response.json()
# Run once: python auth.py <your_auth_code>
if __name__ == "__main__":
import sys
token_data = get_access_token(sys.argv[1])
print(f"Access token: {token_data['access_token']}")
print(f"Refresh token: {token_data['refresh_token']}")
# Store these in your .env file
Expected: JSON with access_token and refresh_token. Save both — access tokens expire in 24 hours, refresh tokens last 365 days.
Step 2: Generate AI Meme Images
# meme_generator.py
import openai
import requests
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
import os
from dotenv import load_dotenv
load_dotenv()
client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
MEME_PROMPTS = [
"A confused developer staring at a screen full of errors, digital art, meme style",
"A cat sitting in a meeting room looking bored, corporate office, meme style",
"A programmer celebrating 'it works' with confetti, cartoon style, meme style",
]
MEME_CAPTIONS = [
("When the bug was there all along", "and it was a missing semicolon"),
("Me in every standup meeting", "pretending I know what's happening"),
("First time my code runs without errors", "delete system32 has entered the chat"),
]
def generate_meme_image(prompt: str, top_text: str, bottom_text: str) -> bytes:
"""Generate a meme: DALL-E creates the base image, Pillow adds text overlay."""
# Generate base image with DALL-E 3
response = client.images.generate(
model="dall-e-3",
prompt=prompt,
size="1024x1024", # Square for TikTok image posts
quality="standard",
n=1
)
image_url = response.data[0].url
image_data = requests.get(image_url).content
img = Image.open(BytesIO(image_data)).convert("RGB")
# Add meme text overlay
img = add_meme_text(img, top_text, bottom_text)
# Return as bytes
output = BytesIO()
img.save(output, format="JPEG", quality=90)
return output.getvalue()
def add_meme_text(img: Image.Image, top_text: str, bottom_text: str) -> Image.Image:
"""Classic white text with black outline, Impact font style."""
draw = ImageDraw.Draw(img)
width, height = img.size
# Use a large font - fallback to default if Impact not available
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/msttcorefonts/Impact.ttf", 60)
except IOError:
font = ImageFont.load_default()
def draw_text_with_outline(text: str, y_position: int):
# Center the text
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
x = (width - text_width) // 2
# Black outline (draw text offset in 8 directions)
outline_color = "black"
for dx, dy in [(-3,-3),(-3,3),(3,-3),(3,3),(-3,0),(3,0),(0,-3),(0,3)]:
draw.text((x + dx, y_position + dy), text, font=font, fill=outline_color)
# White text on top
draw.text((x, y_position), text, font=font, fill="white")
draw_text_with_outline(top_text.upper(), 20)
draw_text_with_outline(bottom_text.upper(), height - 90)
return img
Expected: A bytes object containing a JPEG image with meme text overlaid.
If it fails:
- Font not found: Install
msttcorefonts(sudo apt install ttf-mscorefonts-installer) or use any.ttfpath - OpenAI quota error: DALL-E 3 costs ~$0.04/image — set spend limits in your OpenAI dashboard
Step 3: Upload to TikTok
# tiktok_uploader.py
import requests
import os
from dotenv import load_dotenv
load_dotenv()
TIKTOK_API_BASE = "https://open.tiktokapis.com/v2"
def upload_photo_post(image_bytes: bytes, caption: str) -> dict:
"""
Upload a single image as a TikTok photo post.
TikTok's Content Posting API uses a two-step process:
1. Initialize the upload to get an upload URL
2. POST the actual image bytes
"""
access_token = os.getenv("TIKTOK_ACCESS_TOKEN")
headers = {"Authorization": f"Bearer {access_token}"}
# Step A: Initialize photo post and get upload URL
init_response = requests.post(
f"{TIKTOK_API_BASE}/post/publish/inbox/video/init/",
headers={**headers, "Content-Type": "application/json"},
json={
"post_info": {
"title": caption,
"privacy_level": "PUBLIC_TO_EVERYONE",
"disable_duet": False,
"disable_comment": False,
"disable_stitch": False,
},
"source_info": {
"source": "FILE_UPLOAD",
"video_size": len(image_bytes), # Total bytes
"chunk_size": len(image_bytes), # Single chunk upload
"total_chunk_count": 1
}
}
)
init_data = init_response.json()
if init_response.status_code != 200:
raise Exception(f"Init failed: {init_data}")
upload_url = init_data["data"]["upload_url"]
publish_id = init_data["data"]["publish_id"]
# Step B: Upload the image bytes
upload_response = requests.put(
upload_url,
headers={
"Content-Type": "image/jpeg",
"Content-Range": f"bytes 0-{len(image_bytes)-1}/{len(image_bytes)}"
},
data=image_bytes
)
if upload_response.status_code not in (200, 201):
raise Exception(f"Upload failed: {upload_response.text}")
return {"publish_id": publish_id, "status": "uploaded"}
If it fails:
- 401 Unauthorized: Your access token expired. Use the refresh token flow to get a new one
- 400 Bad Request: Check that
video_sizeexactly matcheslen(image_bytes)
Step 4: Wire It All Together with a Scheduler
# bot.py - The main entry point
import schedule
import time
import random
import logging
from meme_generator import generate_meme_image, MEME_PROMPTS, MEME_CAPTIONS
from tiktok_uploader import upload_photo_post
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
def post_daily_meme():
"""Pick a random prompt + caption, generate, and post."""
logging.info("Starting daily meme post...")
# Pick random combo
prompt = random.choice(MEME_PROMPTS)
top_text, bottom_text = random.choice(MEME_CAPTIONS)
caption = f"{top_text} 😂 #meme #coding #developer #funny"
try:
# Generate the meme
logging.info(f"Generating meme: {top_text}")
image_bytes = generate_meme_image(prompt, top_text, bottom_text)
# Upload to TikTok
result = upload_photo_post(image_bytes, caption)
logging.info(f"Posted successfully: {result['publish_id']}")
except Exception as e:
logging.error(f"Post failed: {e}")
# Don't crash the scheduler — just log and continue
# Schedule for 10 AM daily
schedule.every().day.at("10:00").do(post_daily_meme)
logging.info("Bot started. Posting daily at 10:00 AM.")
while True:
schedule.run_pending()
time.sleep(60) # Check every minute
Verification
Run the bot manually to test the full pipeline:
python -c "from bot import post_daily_meme; post_daily_meme()"
You should see:
2026-02-23 10:00:01 Starting daily meme post...
2026-02-23 10:00:02 Generating meme: When the bug was there all along
2026-02-23 10:00:08 Posted successfully: publish_id_abc123
Check your TikTok profile — the post should appear within 1-2 minutes.
Running It Continuously
For a server or Raspberry Pi:
# Run in background with logs
nohup python bot.py > bot.log 2>&1 &
# Or as a systemd service (recommended for reliability)
# Create /etc/systemd/system/tiktok-bot.service
[Unit]
Description=TikTok Meme Bot
After=network.target
[Service]
ExecStart=/usr/bin/python3 /home/user/tiktok-bot/bot.py
WorkingDirectory=/home/user/tiktok-bot
Restart=always
User=user
[Install]
WantedBy=multi-user.target
sudo systemctl enable tiktok-bot
sudo systemctl start tiktok-bot
What You Learned
- TikTok's Content Posting API requires a two-step upload: initialize first, then stream bytes
- DALL-E 3 returns a temporary URL — download immediately, it expires in ~1 hour
- The
schedulelibrary is fine for simple bots; use Celery or cron for production reliability
Limitations to know:
- TikTok's API rate limits: 100 posts/day max on most app tiers
- DALL-E 3 image URLs expire — always download and store locally if you need them later
- Access tokens expire after 24 hours; build refresh token rotation if running long-term
When NOT to use this approach:
- High-volume posting (100+ posts/day) — use TikTok's Content Marketing API instead
- If you need TikTok analytics or comments — requires additional API scopes
- Fully unattended bots violate TikTok's ToS if they post without human review — check their developer policies first
Tested on Python 3.12, openai 1.x, TikTok Content Posting API v2 — February 2026