threads-client
Production-grade, async-first Python SDK for the Meta Threads Graph API. Built for automated bots, content publishing pipelines, and social media management.
Features
- Async-First Architecture: Built on
httpx.AsyncClientwith non-blocking I/O. - Resilience Engine:
- Automatic retry for Meta error subcode
4279009(Media not ready) with linear backoff. - Exponential backoff retry for transient Meta errors (
HTTP 500,429, and error codes1,2,4,17,341). - Container status polling with configurable timeout and delay.
- Automatic retry for Meta error subcode
- Carousel Publishing: Bounded-concurrency item container creation (
asyncio.Semaphore), automated multi-container status polling, and one-step publishing. - OAuth Token Management: Exchange short-lived tokens for 60-day long-lived tokens and refresh existing tokens.
- CLI Utility: Built-in
threads-clientcommand for automated.envtoken renewal. - Safe Logging: Automatic masking of access tokens in error logs to prevent secret leakage.
- Strict Typing: 100% type-annotated with Pydantic v2 models and strict mypy compliance.
Installation
pip install threads-client
Or using uv:
uv add threads-client
Quickstart
1. Publishing Posts
import asyncio
from threads_client import ThreadsClient
from threads_client.models import CarouselMediaItem
async def main() -> None:
async with ThreadsClient(user_id="YOUR_USER_ID", access_token="YOUR_ACCESS_TOKEN") as client:
# 1. Text Post
result = await client.posts.create(
text="Hello Threads from Python!",
topic_tag="Tech",
)
print(f"Published post ID: {result.post_id}")
# 2. Single Image Post
image_result = await client.posts.create(
text="Check out this chart!",
image_url="https://example.com/chart.png",
topic_tag="Baseball",
)
print(f"Published image post ID: {image_result.post_id}")
# 3. Multi-Media Carousel Post
carousel_result = await client.posts.create_carousel(
text="Game Highlights",
items=[
CarouselMediaItem(media_type="IMAGE", url="https://example.com/card1.png"),
CarouselMediaItem(media_type="VIDEO", url="https://example.com/walkoff.mp4"),
],
topic_tag="MLB",
)
print(f"Published carousel ID: {carousel_result.post_id}")
asyncio.run(main())
2. Convenience / Backward-Compatible Aliases
For minimal friction when migrating existing scripts:
async with ThreadsClient(user_id="...", access_token="...") as client:
post_id = await client.post("Simple text post", topic_tag="News")
carousel_id = await client.post_carousel("Carousel post", items=[...])
3. Listing and Deleting Posts
async with ThreadsClient(user_id="YOUR_USER_ID", access_token="YOUR_ACCESS_TOKEN") as client:
# 1. Automatic cursor pagination across all posts
async for post in client.posts.iter_posts(limit=25):
print(f"[{post.id}] {post.text} ({post.timestamp})")
# 2. Fetch a single page
page = await client.posts.list(limit=10)
for post in page.data:
print(f"[{post.id}] {post.text}")
# 3. Delete a post
success = await client.posts.delete(post_id="POST_ID_TO_DELETE")
print(f"Deleted: {success}")
4. CLI Token Management
Keep long-lived 60-day tokens fresh automatically:
# Refresh long-lived token and update .env automatically
threads-client token refresh --env-file .env
# Exchange short-lived token for long-lived token
threads-client token exchange --short-token <SHORT_TOKEN> --app-secret <APP_SECRET> --env-file .env
Error Handling
All SDK exceptions inherit from ThreadsError:
from threads_client import ThreadsClient
from threads_client.exceptions import (
ThreadsAPIError,
ThreadsAuthenticationError,
ThreadsMediaProcessingError,
ThreadsRateLimitError,
ThreadsTimeoutError,
ThreadsValidationError,
)
try:
async with ThreadsClient(...) as client:
await client.posts.create(...)
except ThreadsAuthenticationError as err:
print(f"Auth failed: {err}")
except ThreadsRateLimitError as err:
print(f"Rate limited: {err}")
except ThreadsMediaProcessingError as err:
print(f"Media container processing failed: {err}")
except ThreadsTimeoutError as err:
print(f"Polling timed out: {err}")
except ThreadsAPIError as err:
print(f"API returned status {err.status_code}, code {err.code}: {err.message}")
Development & Testing
# Run test suite
make test
# Run lint, format, type, and dead code checks
make lint
# Or run individual tools via uv
uv run pytest tests/ -v
uv run ruff check threads_client tests
uv run mypy threads_client tests
uv run vulture threads_client tests
License
MIT License
Release files for threads-client 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| threads_client-0.2.0.tar.gz | 63.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| threads_client-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 81.3 kB
Release files / threads_client-0.2.0.tar.gz
| Download URL | threads_client-0.2.0.tar.gz |
|---|---|
| Size | 63.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
77a0b6da957ffd05d3adc1ac30a24e2361f1d3fc09c72d4d78355540e91d88e5
|
|
BLAKE2b-256 checksum How to use checksums |
baca1859005cd613f39eb325791851d76df12c7d2e434416a5b34baab776d06a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 4, 2026.
Transparency logRelease files / threads_client-0.2.0-py3-none-any.whl
| Download URL | threads_client-0.2.0-py3-none-any.whl |
|---|---|
| Size | 18.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9fb39edb67e81da5da0e9f1afabb9ffb25f9d43d236ab059ebead1c055b2e482
|
|
BLAKE2b-256 checksum How to use checksums |
4a6c0f7f13ac4a4e4dddf8181b25f5a72ef7451806ca7234d528fb54325289e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 4, 2026.
Transparency log