Unofficial Python client for Meta's Threads API. Publish posts, reply to conversations, and track insights with Threads automation.
Project description
meta-threads-sdk
Unofficial Python SDK for Meta's Threads API.
Features
- Sync & Async clients - Choose the right client for your use case
- Full API coverage - Posts, media, insights, replies, user profiles
- Type-safe - Full type hints and Pydantic models
- OAuth 2.0 - Complete authentication flow support
- Rate limiting - Built-in rate limit tracking
- Logging - Configurable logging for debugging
Installation
pip install meta-threads-sdk
Or with uv:
uv add meta-threads-sdk
Quick Start
Synchronous Client
from threads import ThreadsClient
with ThreadsClient(access_token="your_token") as client:
# Create a text post
post = client.posts.create_and_publish(
user_id="your_user_id",
text="Hello from Threads SDK!",
)
print(f"Published: {post.permalink}")
# Get user profile
profile = client.users.get_me()
print(f"Username: {profile.username}")
Asynchronous Client
import asyncio
from threads import AsyncThreadsClient
async def main():
async with AsyncThreadsClient(access_token="your_token") as client:
post = await client.posts.create_and_publish(
user_id="your_user_id",
text="Hello from async Threads SDK!",
)
print(f"Published: {post.permalink}")
asyncio.run(main())
Authentication
OAuth 2.0 Flow
-
Set up your Meta App: Go to Meta Developer Console and create an app with Threads API access.
-
Configure redirect URI: Add your redirect URI in the app settings (e.g.,
https://your-app.com/callback). -
Get authorization:
from threads import ThreadsClient
from threads.constants import Scope
client = ThreadsClient(access_token="")
# Step 1: Generate authorization URL
auth_url = client.auth.get_authorization_url(
client_id="your_app_id",
redirect_uri="https://your-app.com/callback",
scopes=[
Scope.BASIC,
Scope.CONTENT_PUBLISH,
Scope.MANAGE_INSIGHTS,
Scope.READ_REPLIES,
Scope.MANAGE_REPLIES,
],
)
print(f"Open this URL: {auth_url}")
# Step 2: After user authorizes, exchange code for token
short_token = client.auth.exchange_code(
client_id="your_app_id",
client_secret="your_app_secret",
redirect_uri="https://your-app.com/callback",
code="authorization_code_from_callback",
)
print(f"Short-lived token: {short_token.access_token}")
print(f"User ID: {short_token.user_id}")
# Step 3: Get long-lived token (60 days)
long_token = client.auth.get_long_lived_token(
client_secret="your_app_secret",
short_lived_token=short_token.access_token,
)
print(f"Long-lived token: {long_token.access_token}")
print(f"Expires in: {long_token.expires_in} seconds")
# Step 4: Refresh token before expiry
refreshed = client.auth.refresh_long_lived_token(long_token.access_token)
API Reference
Posts
from threads.constants import ReplyControl
# Create and publish a text post
post = client.posts.create_and_publish(
user_id="123",
text="Hello, Threads!",
)
# Create post with image
post = client.posts.create_and_publish(
user_id="123",
text="Check out this photo!",
image_url="https://example.com/image.jpg",
)
# Create post with video
post = client.posts.create_and_publish(
user_id="123",
text="Watch this video!",
video_url="https://example.com/video.mp4",
wait_for_ready=True, # Wait for video processing
)
# Control who can reply
post = client.posts.create_and_publish(
user_id="123",
text="Only my followers can reply",
reply_control=ReplyControl.ACCOUNTS_YOU_FOLLOW,
)
# Get a post
post = client.posts.get("post_id")
# Get user's posts
posts = client.posts.get_user_posts("user_id", limit=10)
# Delete a post
client.posts.delete("post_id")
# Check publishing limits (250 posts / 1000 replies per 24h)
limit = client.posts.get_publishing_limit("user_id")
print(f"Posts: {limit.quota_usage}/{limit.quota_total}")
print(f"Remaining: {limit.remaining_posts}")
Replies
# Reply to a post
reply = client.posts.create_and_publish(
user_id="123",
text="This is my reply!",
reply_to_id="original_post_id",
)
# Get replies to a post
replies = client.replies.get_replies("post_id")
# Get user's replies
my_replies = client.replies.get_user_replies("user_id", limit=10)
# Get conversation thread
conversation = client.replies.get_conversation("post_id")
# Manage reply visibility
client.replies.hide("reply_id")
client.replies.unhide("reply_id")
Media (Images, Videos, Carousels)
# Create image container
container = client.media.create_image_container(
user_id="123",
image_url="https://example.com/image.jpg",
text="Caption",
)
# Create video container
container = client.media.create_video_container(
user_id="123",
video_url="https://example.com/video.mp4",
text="Video caption",
)
# Check container status (for videos)
status = client.media.get_container_status(container.id)
print(f"Status: {status.status}") # IN_PROGRESS, FINISHED, ERROR
# Create carousel (multi-image post)
import time
# Step 1: Create child containers
child_ids = []
for image_url in image_urls:
container = client.media.create_image_container(
user_id="123",
image_url=image_url,
is_carousel_item=True,
)
# Wait for each child to be ready
while True:
status = client.media.get_container_status(container.id)
if status.is_ready:
child_ids.append(container.id)
break
if status.has_error:
raise Exception(status.error_message)
time.sleep(1)
# Step 2: Create carousel container
carousel = client.media.create_carousel_container(
user_id="123",
children=child_ids,
text="Swipe to see more!",
)
# Step 3: Wait for carousel to be ready
while True:
status = client.media.get_container_status(carousel.id)
if status.is_ready:
break
time.sleep(1)
# Step 4: Publish
post = client.posts.publish("123", carousel.id)
User Profile
# Get current user's profile
me = client.users.get_me()
print(f"Username: {me.username}")
print(f"Bio: {me.biography}")
# Get another user's profile
user = client.users.get("user_id")
Insights
from threads.constants import MetricType
# Get post metrics
insights = client.insights.get_media_insights("post_id")
print(f"Views: {insights.views}")
print(f"Likes: {insights.likes}")
print(f"Replies: {insights.replies}")
print(f"Reposts: {insights.reposts}")
print(f"Quotes: {insights.quotes}")
# Get specific metrics
insights = client.insights.get_media_insights(
"post_id",
metrics=[MetricType.VIEWS, MetricType.LIKES],
)
# Get user-level insights
user_insights = client.insights.get_user_insights("user_id")
views = user_insights.get_metric("views")
followers = user_insights.get_metric("followers_count")
Error Handling
from threads.exceptions import (
ThreadsAPIError,
AuthenticationError,
RateLimitError,
ValidationError,
ContainerError,
)
try:
post = client.posts.create_and_publish(user_id="123", text="Hello!")
except AuthenticationError:
print("Invalid or expired token")
except RateLimitError as e:
print(f"Rate limited. Retry after: {e.retry_after}s")
except ValidationError as e:
print(f"Invalid input: {e.message}")
except ContainerError as e:
print(f"Media processing failed: {e.message}")
except ThreadsAPIError as e:
print(f"API error: {e.message} (code: {e.error_code})")
Logging
Enable logging to debug API calls:
from threads import setup_logging
import logging
# Enable debug logging
setup_logging(level=logging.DEBUG)
# Or configure specific loggers
setup_logging(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
Rate Limits
The Threads API has the following limits:
- 250 posts per 24-hour rolling window
- 1000 replies per 24-hour rolling window
Check your current usage:
limit = client.posts.get_publishing_limit("user_id")
print(f"Posts used: {limit.quota_usage}/{limit.quota_total}")
print(f"Remaining posts: {limit.remaining_posts}")
Development
# Clone the repository
git clone https://github.com/MetaThreads/meta-threads-sdk.git
cd meta-threads-sdk
# Install uv if you haven't
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install dependencies
uv sync --dev
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=src/threads --cov-report=term-missing
# Run linter
uv run ruff check .
# Run type checker
uv run mypy src
License
MIT License - see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file meta_threads_sdk-0.2.1.tar.gz.
File metadata
- Download URL: meta_threads_sdk-0.2.1.tar.gz
- Upload date:
- Size: 108.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36caec068ca974904cd251de16124de356eae042913abce4cbfea11550575803
|
|
| MD5 |
2b7d7af0aec044eb9df4f377db537bcf
|
|
| BLAKE2b-256 |
1d5978eb14d8768968acd03b212015a9ac478e1540bd013a97bdbcfb1bc858d9
|
File details
Details for the file meta_threads_sdk-0.2.1-py3-none-any.whl.
File metadata
- Download URL: meta_threads_sdk-0.2.1-py3-none-any.whl
- Upload date:
- Size: 50.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2241bc5684ccb62523b7c23a337cb1a51bf0742ad0e180da3aa886cae49832a
|
|
| MD5 |
1c5d57f33f3a36e74d8e315ee5621084
|
|
| BLAKE2b-256 |
fcaf64ef2cd1f8ba46fd33c6baa7847857a901abe3cdc4393a8631eaa11a05df
|