Threads APIthreads.net ↗
Search Threads.net posts and user profiles by keyword. Returns post text, engagement metrics, author info, verification status, and pagination cursors.
What is the Threads API?
The Threads.net API provides 2 endpoints for searching posts and user accounts on Threads. The search_posts endpoint returns matching threads with full engagement metrics — like count, reply count, repost count, and quote count — alongside author info and timestamps. The search_users endpoint returns profile data including verification status and whether the account is active on Threads, supporting lookups of up to 50 users per request.
curl -X GET 'https://api.parse.bot/scraper/48546944-bfeb-482c-9503-061e6b392624/search_posts' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace threads-net-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""
Threads Search API Client
Practical example for searching posts and users on Threads (threads.net)
Get your API key from: https://parse.bot/settings
"""
import os
import requests
from typing import Optional
from datetime import datetime
class ParseClient:
"""Client for interacting with the Threads Search API via Parse."""
def __init__(self, api_key: Optional[str] = None):
self.base_url = "https://api.parse.bot"
self.scraper_id = "48546944-bfeb-482c-9503-061e6b392624"
self.api_key = api_key or os.getenv("PARSE_API_KEY")
if not self.api_key:
raise ValueError(
"API key not provided. Set PARSE_API_KEY environment variable or pass it to the constructor."
)
def _call(
self, endpoint: str, method: str = "POST", **params
) -> dict:
"""
Make a request to the Parse API.
Args:
endpoint: The endpoint name (e.g., 'search_posts')
method: HTTP method - 'GET' or 'POST'
**params: Query/payload parameters
Returns:
Response JSON as dictionary
"""
url = f"{self.base_url}/scraper/{self.scraper_id}/{endpoint}"
headers = {
"X-API-Key": self.api_key,
"Content-Type": "application/json"
}
if method == "GET":
response = requests.get(url, headers=headers, params=params)
elif method == "POST":
response = requests.post(url, headers=headers, json=params)
else:
raise ValueError(f"Unsupported HTTP method: {method}")
response.raise_for_status()
return response.json()
def search_posts(
self,
query: str,
search_surface: str = "default"
) -> dict:
"""
Search for posts/threads on Threads by keyword.
Args:
query: Search keyword or phrase to find posts about
search_surface: Search result ordering - 'default', 'top', or 'recent'
Returns:
Dictionary containing posts array and pagination info
"""
return self._call(
"search_posts",
method="GET",
query=query,
search_surface=search_surface
)
def search_users(
self,
query: str,
limit: int = 10
) -> dict:
"""
Search for user accounts on Threads by keyword.
Args:
query: Search keyword or username to find user accounts
limit: Maximum number of user results to return (1-50)
Returns:
Dictionary containing users array
"""
return self._call(
"search_users",
method="GET",
query=query,
limit=limit
)
def main():
"""Practical workflow example: Find and analyze posts from popular tech accounts."""
client = ParseClient()
print("=" * 60)
print("THREADS SEARCH API - PRACTICAL WORKFLOW EXAMPLE")
print("=" * 60)
search_topic = "artificial intelligence"
print(f"\n📱 Step 1: Searching for users interested in '{search_topic}'...")
users_response = client.search_users(query=search_topic, limit=5)
tech_users = users_response.get("users", [])
if not tech_users:
print("No users found for this search.")
return
print(f"✅ Found {len(tech_users)} tech users:")
for i, user in enumerate(tech_users, 1):
status = "✓" if user.get("is_active_on_threads") else "✗"
verified = "✓" if user.get("is_verified") else " "
print(
f" {i}. @{user['username']} ({user['full_name']}) "
f"[Active: {status}] [Verified: {verified}]"
)
print(f"\n🔍 Step 2: Searching for recent posts about '{search_topic}'...")
posts_response = client.search_posts(query=search_topic, search_surface="recent")
posts = posts_response.get("posts", [])
if not posts:
print("No posts found for this search.")
return
print(f"✅ Found {len(posts)} posts about '{search_topic}':")
print()
total_engagement = 0
engagement_by_user = {}
for i, post in enumerate(posts[:5], 1):
user = post.get("user", {})
username = user.get("username", "unknown")
engagement = (
post.get("like_count", 0)
+ post.get("reply_count", 0)
+ post.get("repost_count", 0)
+ post.get("quote_count", 0)
)
total_engagement += engagement
if username not in engagement_by_user:
engagement_by_user[username] = {
"posts": 0,
"total_engagement": 0,
"user_info": user
}
engagement_by_user[username]["posts"] += 1
engagement_by_user[username]["total_engagement"] += engagement
timestamp = datetime.fromtimestamp(post.get("taken_at", 0)).strftime(
"%Y-%m-%d %H:%M:%S"
)
text_preview = post.get("text", "")[:100]
if len(post.get("text", "")) > 100:
text_preview += "..."
print(f"Post {i} by @{username}")
print(f" 📝 {text_preview}")
print(f" 📊 Engagement: ❤️ {post.get('like_count', 0)} | "
f"💬 {post.get('reply_count', 0)} | "
f"🔄 {post.get('repost_count', 0)} | "
f"💭 {post.get('quote_count', 0)}")
print(f" ⏱️ Posted: {timestamp}")
print()
print("=" * 60)
print("📊 ENGAGEMENT ANALYSIS")
print("=" * 60)
print(f"Total posts analyzed: {len(posts[:5])}")
print(f"Total engagement: {total_engagement}")
print(f"Average engagement per post: {total_engagement / len(posts[:5]):.1f}")
print()
print("Top engaging users:")
sorted_users = sorted(
engagement_by_user.items(),
key=lambda x: x[1]["total_engagement"],
reverse=True
)
for rank, (username, stats) in enumerate(sorted_users[:3], 1):
user_info = stats["user_info"]
verified = "✓" if user_info.get("is_verified") else " "
print(
f" {rank}. @{username} - "
f"Posts: {stats['posts']}, "
f"Total Engagement: {stats['total_engagement']} "
f"[Verified: {verified}]"
)
print("\n✨ Workflow complete!")
if __name__ == "__main__":
main()Search for posts/threads on Threads by keyword. Returns posts matching the search query with engagement metrics, author info, and post text.
| Param | Type | Description |
|---|---|---|
| queryrequired | string | Search keyword or phrase to find posts about. |
| search_surface | string | Search result ordering. Accepts exactly one of: default, top, recent. |
{
"type": "object",
"fields": {
"posts": "array of post objects with id, code, text, like_count, reply_count, repost_count, quote_count, taken_at, and user info",
"end_cursor": "string or null pagination cursor",
"has_next_page": "boolean"
},
"sample": {
"posts": [
{
"id": "3908520567213299340",
"code": "DY92r9mDpaM",
"text": "At AltmanAI, we believe the future of AI should feel human...",
"user": {
"pk": "78025189002",
"username": "altmanai_",
"full_name": "AltmanAI",
"is_verified": false,
"profile_pic_url": "https://scontent-ord5-2.cdninstagram.com/..."
},
"taken_at": 1780151981,
"like_count": 1,
"quote_count": 0,
"reply_count": 0,
"repost_count": 0
}
],
"end_cursor": null,
"has_next_page": false
}
}About the Threads API
Endpoints Overview
The API exposes two endpoints: search_posts and search_users. Both accept a query string as the primary required input and return structured arrays of matching objects. Responses from search_posts include a has_next_page boolean and an end_cursor string to support paginated retrieval of large result sets.
search_posts
The search_posts endpoint accepts a query string and an optional search_surface parameter that controls result ordering. Valid values for search_surface are default, top, and recent — allowing you to retrieve the most relevant results or the most recent ones depending on your use case. Each post object in the response includes id, code, text, like_count, reply_count, repost_count, quote_count, and taken_at (the post timestamp), plus a nested user object with author details.
search_users
The search_users endpoint accepts a query string and an optional limit integer (1–50) capping the number of returned profiles. Each user object includes username, full_name, pk (the platform's internal user identifier), profile_pic_url, is_verified, and is_active_on_threads. The is_active_on_threads flag distinguishes accounts that are linked from Instagram but not actively posting on Threads from those with genuine Threads activity.
The Threads API is a managed, monitored endpoint for threads.net — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when threads.net changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official threads.net API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Track engagement trends for a keyword by comparing
like_count,repost_count, andquote_countacrosstopvsrecentsearch surfaces - Monitor brand mentions on Threads by querying a company or product name through
search_posts - Identify verified creators in a topic area using
is_verifiedfromsearch_usersresults - Build a Threads content aggregator by paginating
search_postsresults usingend_cursorandhas_next_page - Find accounts active on Threads (not just linked from Instagram) using the
is_active_on_threadsfield - Analyze post volume and reply activity for a hashtag or keyword over time using
taken_atandreply_count - Compile a list of usernames and profile images matching a topic for outreach or research using
search_users
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 100 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
One credit = one API call regardless of which marketplace API you call. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does Threads have an official developer API?+
What does the search_surface parameter actually change in search_posts results?+
search_surface to top returns posts ranked by relevance or engagement. recent returns posts in reverse-chronological order. default applies the platform's standard ranking. The response shape — including all engagement fields and pagination cursors — is identical across all three values; only the ordering of results differs.Does the API return follower counts or following counts for users?+
search_users endpoint returns username, full_name, pk, profile_pic_url, is_verified, and is_active_on_threads, but does not expose follower or following counts. You can fork this API on Parse and revise it to add a profile detail endpoint that includes those fields.Can I retrieve the full reply thread or quoted post content for a given post?+
search_posts endpoint returns top-level post fields including reply_count and quote_count as counts, but does not return the content of replies or quoted posts. You can fork this API on Parse and revise it to add an endpoint that fetches a post's replies or quote-thread content.How does pagination work for search_posts?+
search_posts response includes has_next_page (boolean) and end_cursor (string or null). When has_next_page is true, pass the value of end_cursor as a cursor parameter in your next request to retrieve the following page of results. When end_cursor is null, you have reached the last page.