A Threads scraper, for a developer, is a small set of reads: the profile, the posts that profile shows publicly, and a keyword search. SocialScrape has those three as GET routes. All three are in beta. You do not create a Threads developer app to call them. You send a SocialScrape key in x-api-key. See authentication.
Profile#
GET /v1/threads/profile requires handle, the username with or without @. username is an alias. Errors are invalid_params and not_found.
The recorded request uses a public handle.
curl -G "https://api.socialscrape.io/v1/threads/profile" \
-H "x-api-key: $SOCIALSCRAPE_API_KEY" \
--data-urlencode "handle=zuck"import os
import requests
params = {
"handle": "zuck",
}
res = requests.get(
"https://api.socialscrape.io/v1/threads/profile",
headers={"x-api-key": os.environ["SOCIALSCRAPE_API_KEY"]},
params=params,
)
body = res.json()
if body["ok"]:
print(body["data"])
else:
print(body["error"]["code"], body["error"]["message"])const url = new URL("https://api.socialscrape.io/v1/threads/profile");
url.search = new URLSearchParams({
"handle": "zuck"
}).toString();
const res = await fetch(url, {
headers: { "x-api-key": process.env.SOCIALSCRAPE_API_KEY! },
});
const body = await res.json();
if (body.ok) {
console.log(body.data);
} else {
console.error(body.error.code, body.error.message);
}data is a profile with platform threads. Fields you can rely on being present as keys, and null when Threads omits them: id, handle, name, bio, avatar_url, banner_url, url, verified, is_private, external_url, links, headline, location, category, created_at.
stats.followers and stats.posts are the public counts. stats.following is null in the recorded sample, which matches a profile page that does not show a following count. Do not assume every profile fills it. stats.likes and stats.views follow the same rule.
extra on the recorded profile has has_onboarded and topics. That is the extra block for this sample, not a promise that every account has topics filled.
Sample. The recording is shortened. Treat every integer in it as sample data.
Recent posts#
GET /v1/threads/profile/posts returns the most recent posts, newest first, about 15 per page. handle is required. cursor asks for the next page.
The limit is upstream. Threads shows logged-out visitors only the latest 20 to 30 posts of a profile, so paging runs out early. has_more goes false because that window ended, not because you have the account's whole history.
curl -G "https://api.socialscrape.io/v1/threads/profile/posts" \
-H "x-api-key: $SOCIALSCRAPE_API_KEY" \
--data-urlencode "handle=zuck"import os
import requests
params = {
"handle": "zuck",
}
res = requests.get(
"https://api.socialscrape.io/v1/threads/profile/posts",
headers={"x-api-key": os.environ["SOCIALSCRAPE_API_KEY"]},
params=params,
)
body = res.json()
if body["ok"]:
print(body["data"])
else:
print(body["error"]["code"], body["error"]["message"])const url = new URL("https://api.socialscrape.io/v1/threads/profile/posts");
url.search = new URLSearchParams({
"handle": "zuck"
}).toString();
const res = await fetch(url, {
headers: { "x-api-key": process.env.SOCIALSCRAPE_API_KEY! },
});
const body = await res.json();
if (body.ok) {
console.log(body.data);
} else {
console.error(body.error.code, body.error.message);
}Each item is a post: id, url, text, published_at, author, media, stats. stats.likes, stats.comments and stats.shares are the counts when Threads sends them. stats.views and stats.saves are null when they are absent. extra on a feed post includes code, full_id, quotes, reply_control, reposts and shares_sent.
parent is the profile. Follow cursor while has_more is true, and stop when it is false. Each page is 1 credit. See pagination.
One post, with its top replies, is GET /v1/threads/post. The parameter is url, a threads.net/@user/post/... or threads.com post link. Replies are in extra.replies. The feed does not inline that tree. The recorded post URL in the reference is a public threads.com link, and that sample shows likes, comments and shares filled, with views null.
Search#
GET /v1/threads/search finds posts for a keyword. query is required. keyword and q are aliases. You get one page of about 15 to 20 results, and no further pages. The reference says there is no paging.
start_date and end_date (YYYY-MM-DD) filter that page by post date. Threads does not apply the dates itself. The endpoint filters the page it got. A date filter cannot surface posts that were not on the page.
The recorded search term is basketball.
curl -G "https://api.socialscrape.io/v1/threads/search" \
-H "x-api-key: $SOCIALSCRAPE_API_KEY" \
--data-urlencode "query=basketball"import os
import requests
params = {
"query": "basketball",
}
res = requests.get(
"https://api.socialscrape.io/v1/threads/search",
headers={"x-api-key": os.environ["SOCIALSCRAPE_API_KEY"]},
params=params,
)
body = res.json()
if body["ok"]:
print(body["data"])
else:
print(body["error"]["code"], body["error"]["message"])const url = new URL("https://api.socialscrape.io/v1/threads/search");
url.search = new URLSearchParams({
"query": "basketball"
}).toString();
const res = await fetch(url, {
headers: { "x-api-key": process.env.SOCIALSCRAPE_API_KEY! },
});
const body = await res.json();
if (body.ok) {
console.log(body.data);
} else {
console.error(body.error.code, body.error.message);
}Items match the post shape above. extra on a search hit includes code, full_id, quotes, reply_control and reposts. There is no second page to request. If you need a different slice, change query.
Cost#
Each successful profile, page of posts, search or single post is 1 credit. All four are cacheable for 30 days. max_age_days makes a fresh-enough repeat free. Failures are free. The signup grant is 100 credits, once, with no card. Prices are on pricing. Rules are in credits.
Limits#
Plan on the logged-out window: roughly the latest 20 to 30 posts on a profile, delivered about 15 at a time. Search is a single page of about 15 to 20. Dates do not expand search. They only drop rows. Replies are a separate call per post URL.
This is public text and counts. It is not a private reply, a close-friends post, or a follower graph. not_found and invalid_params are final. rate_limited is not: wait retry-after. Free credits are 30 requests a minute per team. See rate limits and errors.
The API is in early access. These routes are beta.
Next#
Profile: Threads profile API, tutorial, reference. Posts: profile posts API and reference. Search: Threads search API and reference. The hub is the Threads API.
Using search as one source in a multi-platform listen is social listening. The Bluesky equivalent is Bluesky profiles and posts.