Why Python Developers Need Threads Data Access
Threads has over 320 million monthly active users and growing. For researchers, analysts, and developers, that represents a substantial dataset — brand sentiment, trend tracking, academic social media analysis, competitive intelligence.
The problem: the official Meta Threads API is locked behind OAuth approval, requires a business account, and imposes strict rate limits (250 posts/24 hours, 500 searches/7 days). For programmatic research or production tooling, those constraints make the official API impractical.
thredly provides a straightforward REST API that bypasses this friction. No OAuth dance, no business account approval, no scraping brittle HTML. Just authenticated HTTP requests returning clean JSON — designed specifically for threads api python workflows.
This tutorial covers everything from fetching a single profile to building an analytics script that aggregates engagement data across endpoints.
Prerequisites
- Python 3.8 or higher
requestslibrary (pip install requests)- A RapidAPI account (free tier available)
- thredly API key from the RapidAPI marketplace
Set your API key as an environment variable to avoid hardcoding credentials:
export RAPIDAPI_KEY="your_api_key_here"
Then load it in Python:
import os
import requests
API_KEY = os.environ.get("RAPIDAPI_KEY")
BASE_URL = "https://threads-api-pro.p.rapidapi.com"
HEADERS = {
"X-RapidAPI-Key": API_KEY,
"X-RapidAPI-Host": "threads-api-pro.p.rapidapi.com"
}
All code examples in this tutorial assume these variables are defined.
Fetch a User Profile
The profile endpoint returns core account metadata. This is typically the first call in any threads api python tutorial workflow — you confirm the user exists and retrieve baseline metrics before fetching content.
def get_user_profile(username: str) -> dict:
url = f"{BASE_URL}/api/user/{username}"
response = requests.get(url, headers=HEADERS)
response.raise_for_status()
return response.json()
profile = get_user_profile("zuck")
print(profile)
Response structure:
{
"success": true,
"data": {
"username": "zuck",
"full_name": "Mark Zuckerberg",
"biography": "...",
"follower_count": 3200000,
"following_count": 500,
"post_count": 847,
"is_verified": true,
"profile_pic_url": "https://..."
}
}
Key fields:
follower_count/following_count— audience size and follow ratiopost_count— total posts published, useful for calculating posting frequencyis_verified— Meta verified statusbiography— bio text for keyword or sentiment analysis
Get User Posts
Fetch recent posts for any public account. This endpoint supports cursor-based pagination, which is essential for how to get threads data with python at scale — iterating through hundreds of posts without missing any.
def get_user_posts(username: str, cursor: str = None) -> dict:
url = f"{BASE_URL}/api/user/{username}/posts"
params = {}
if cursor:
params["cursor"] = cursor
response = requests.get(url, headers=HEADERS, params=params)
response.raise_for_status()
return response.json()
def get_all_posts(username: str) -> list:
all_posts = []
cursor = None
while True:
data = get_user_posts(username, cursor)
posts = data.get("data", {}).get("posts", [])
all_posts.extend(posts)
# Check for next page
cursor = data.get("data", {}).get("next_cursor")
if not cursor:
break
return all_posts
posts = get_all_posts("zuck")
print(f"Fetched {len(posts)} posts")
Each post object includes:
{
"id": "17841234567890",
"text": "Post content here...",
"timestamp": "2026-02-20T14:30:00Z",
"like_count": 45200,
"reply_count": 1840,
"repost_count": 2100,
"media_type": "text"
}
The next_cursor value is null when you have reached the last page. The loop exits cleanly without any additional state tracking.
Search Threads
The search endpoint enables threads data extraction python workflows that monitor keywords, hashtags, or brand mentions across the platform. This is useful for trend analysis, competitive monitoring, and academic research.
def search_users(keyword: str) -> dict:
url = f"{BASE_URL}/api/search/users"
params = {"q": keyword}
response = requests.get(url, headers=HEADERS, params=params)
response.raise_for_status()
return response.json()
results = search_users("machine learning")
users = results.get("data", {}).get("users", [])
for user in users:
print(f"{user['username']} — {user['follower_count']} followers")
The search endpoint returns user accounts matching the query. Each result includes profile metadata, making it straightforward to build discovery pipelines — find relevant accounts, then feed those usernames into the profile and posts endpoints for deeper analysis.
Get Followers and Following
Fetch follower lists for audience research, influencer mapping, or community graph analysis. This is one of the most in-demand capabilities for get threads data programmatically use cases.
def get_followers(username: str, cursor: str = None) -> dict:
url = f"{BASE_URL}/api/user/{username}/followers"
params = {}
if cursor:
params["cursor"] = cursor
response = requests.get(url, headers=HEADERS, params=params)
response.raise_for_status()
return response.json()
def get_all_followers(username: str) -> list:
all_followers = []
cursor = None
while True:
data = get_followers(username, cursor)
followers = data.get("data", {}).get("users", [])
all_followers.extend(followers)
cursor = data.get("data", {}).get("next_cursor")
if not cursor:
break
return all_followers
followers = get_all_followers("someuser")
print(f"Total followers fetched: {len(followers)}")
Each follower entry contains username, full_name, follower_count, and is_verified — enough data to segment audiences by account size or verification status without additional lookups.
Export Data to CSV and JSON
Raw API responses are useful for programmatic analysis, but researchers often need portable formats for spreadsheets, databases, or sharing with collaborators. Python’s standard library handles both CSV and JSON export with no additional dependencies.
Export to JSON
import json
def save_json(data: list, filename: str) -> None:
with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
print(f"Saved {len(data)} records to {filename}")
posts = get_all_posts("someuser")
save_json(posts, "threads_posts.json")
Export to CSV
import csv
def save_posts_csv(posts: list, filename: str) -> None:
if not posts:
print("No posts to save")
return
fieldnames = ["id", "text", "timestamp", "like_count", "reply_count", "repost_count"]
with open(filename, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore")
writer.writeheader()
writer.writerows(posts)
print(f"Saved {len(posts)} posts to {filename}")
save_posts_csv(posts, "threads_posts.csv")
The extrasaction="ignore" parameter drops any fields not in fieldnames, so the code stays stable even if the API adds new response fields.
Error Handling
Production-grade threads api python example code needs to handle network failures, invalid usernames, rate limit responses, and unexpected API errors. A consistent pattern prevents silent failures.
import time
def safe_request(url: str, params: dict = None, retries: int = 3) -> dict | None:
for attempt in range(retries):
try:
response = requests.get(url, headers=HEADERS, params=params, timeout=10)
if response.status_code == 404:
print(f"Resource not found: {url}")
return None
if response.status_code == 429:
wait_time = 2 ** attempt # Exponential backoff: 1s, 2s, 4s
print(f"Rate limited. Waiting {wait_time}s before retry {attempt + 1}/{retries}")
time.sleep(wait_time)
continue
response.raise_for_status()
return response.json()
except requests.exceptions.Timeout:
print(f"Request timed out (attempt {attempt + 1}/{retries})")
except requests.exceptions.ConnectionError:
print(f"Connection error (attempt {attempt + 1}/{retries})")
except requests.exceptions.HTTPError as e:
print(f"HTTP error: {e}")
return None
print(f"All {retries} retries failed for {url}")
return None
Key behaviors in this pattern:
- 404: Non-retryable. Username does not exist or account is private. Return
Noneand continue. - 429: Rate limited. Exponential backoff before retry. Most rate limit windows reset within seconds.
- Timeout: Retry with the same backoff logic.
- HTTPError: Log and return
Nonerather than crashing the entire pipeline.
Replace direct requests.get() calls with safe_request() in any long-running data collection script.
Build a Simple Analytics Script
Combining endpoints gives you a complete picture of any public Threads account. This script uses threads api no login access to calculate engagement rate, identify top-performing posts, and measure posting frequency.
import os
import json
import statistics
from datetime import datetime, timezone
def analyze_threads_account(username: str) -> dict:
print(f"Analyzing @{username}...")
# 1. Fetch profile
profile_data = safe_request(f"{BASE_URL}/api/user/{username}")
if not profile_data or not profile_data.get("success"):
return {"error": f"Could not fetch profile for {username}"}
profile = profile_data["data"]
follower_count = profile.get("follower_count", 0)
# 2. Fetch posts (first page only for quick analysis)
posts_data = safe_request(f"{BASE_URL}/api/user/{username}/posts")
posts = posts_data.get("data", {}).get("posts", []) if posts_data else []
if not posts:
return {"error": "No posts found"}
# 3. Calculate engagement metrics
engagement_scores = []
for post in posts:
likes = post.get("like_count", 0)
replies = post.get("reply_count", 0)
reposts = post.get("repost_count", 0)
total_engagement = likes + replies + reposts
if follower_count > 0:
rate = (total_engagement / follower_count) * 100
engagement_scores.append(rate)
avg_engagement_rate = statistics.mean(engagement_scores) if engagement_scores else 0
# 4. Find top post
top_post = max(
posts,
key=lambda p: p.get("like_count", 0) + p.get("reply_count", 0) + p.get("repost_count", 0)
)
# 5. Calculate posting frequency
timestamps = []
for post in posts:
ts = post.get("timestamp")
if ts:
timestamps.append(datetime.fromisoformat(ts.replace("Z", "+00:00")))
posting_frequency = None
if len(timestamps) >= 2:
timestamps.sort()
total_days = (timestamps[-1] - timestamps[0]).days or 1
posting_frequency = round(len(timestamps) / total_days, 2)
return {
"username": username,
"full_name": profile.get("full_name"),
"follower_count": follower_count,
"posts_analyzed": len(posts),
"avg_engagement_rate_pct": round(avg_engagement_rate, 4),
"top_post": {
"id": top_post.get("id"),
"text_preview": top_post.get("text", "")[:100],
"like_count": top_post.get("like_count"),
"reply_count": top_post.get("reply_count"),
},
"posts_per_day": posting_frequency,
}
if __name__ == "__main__":
result = analyze_threads_account("zuck")
print(json.dumps(result, indent=2))
Sample output:
{
"username": "zuck",
"full_name": "Mark Zuckerberg",
"follower_count": 3200000,
"posts_analyzed": 20,
"avg_engagement_rate_pct": 1.4832,
"top_post": {
"id": "17841234567890",
"text_preview": "Excited to share what we've been building...",
"like_count": 98400,
"reply_count": 4210
},
"posts_per_day": 0.71
}
This output is ready to pipe into a database, a dashboard, or a research report without further transformation. Extend the script by calling get_all_posts() instead of the single-page fetch for complete historical analysis.
Next Steps
You now have a working Python foundation for scrape threads python-style workflows — without the fragility of actual scraping. The thredly API handles session management, rate limit rotation, and response normalization so your code stays clean.
Where to go from here:
- Getting Started with the Threads API — full setup walkthrough including cURL and JavaScript examples
- Threads API vs Web Scraping — why API access is more reliable than scraping for production use
- Track Threads Engagement Metrics — deeper engagement analysis with trend tracking over time
- Threads API TypeScript Guide — same workflows implemented in TypeScript for Node.js projects
- RapidAPI Playground — test endpoints interactively before writing any code
For high-volume research or production pipelines, review the pricing page for rate limit tiers that match your request volume.