Skip to main content

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.

PyPI version Python 3.13+ License: MIT

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

  1. Set up your Meta App: Go to Meta Developer Console and create an app with Threads API access.

  2. Configure redirect URI: Add your redirect URI in the app settings (e.g., https://your-app.com/callback).

  3. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

meta_threads_sdk-0.2.0.tar.gz (108.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

meta_threads_sdk-0.2.0-py3-none-any.whl (50.9 kB view details)

Uploaded Python 3

File details

Details for the file meta_threads_sdk-0.2.0.tar.gz.

File metadata

  • Download URL: meta_threads_sdk-0.2.0.tar.gz
  • Upload date:
  • Size: 108.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for meta_threads_sdk-0.2.0.tar.gz
Algorithm Hash digest
SHA256 a4de73847f2989f676c69f2cb13ee99fc7b8e0b1f6854253ec87e6d9c19af3a5
MD5 6742a285fefe2067d9715d2041a38c31
BLAKE2b-256 8b1b705865e4e1689688eb7f1c6e841315cbea15b1595dbb8c487243f1032bc8

See more details on using hashes here.

File details

Details for the file meta_threads_sdk-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for meta_threads_sdk-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8df1ee63ab4cd3a346f39cad72888cf51a7e9cb2765b73dcf572a5e1b70b05c5
MD5 9700717ba56568e50de313af875dbfaa
BLAKE2b-256 c218c492d4afb41e640c05cec4b7a1da7e949e73c948f78c438e0f25a02c7bd2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page