A Threads scraper, for a developer, is a small set of reads: the profile, the posts that profile shows, and a keyword search. SocialScrape has those three as GET routes. 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 real handle.
1curl -G "https://api.socialscrape.dev/v1/threads/profile" \2 -H "x-api-key: $SOCIALSCRAPE_API_KEY" \3 --data-urlencode "handle=zuck"1import os2import requests34params = {5 "handle": "zuck",6}7res = requests.get(8 "https://api.socialscrape.dev/v1/threads/profile",9 headers={"x-api-key": os.environ["SOCIALSCRAPE_API_KEY"]},10 params=params,11)12body = res.json()1314if body["ok"]:15 print(body["data"])16else:17 print(body["error"]["code"], body["error"]["message"])1const url = new URL("https://api.socialscrape.dev/v1/threads/profile");2url.search = new URLSearchParams({3 "handle": "zuck"4}).toString();5const res = await fetch(url, {6 headers: { "x-api-key": process.env.SOCIALSCRAPE_API_KEY! },7});8const body = await res.json();910if (body.ok) {11 console.log(body.data);12} else {13 console.error(body.error.code, body.error.message);14}data- is a profile with
platformthreads. 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.followersandstats.posts- are the counts.
stats.followingis 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.likesandstats.viewsfollow the same rule. extra- on the recorded profile has
has_onboardedandtopics. 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 gives 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.
1curl -G "https://api.socialscrape.dev/v1/threads/profile/posts" \2 -H "x-api-key: $SOCIALSCRAPE_API_KEY" \3 --data-urlencode "handle=zuck"1import os2import requests34params = {5 "handle": "zuck",6}7res = requests.get(8 "https://api.socialscrape.dev/v1/threads/profile/posts",9 headers={"x-api-key": os.environ["SOCIALSCRAPE_API_KEY"]},10 params=params,11)12body = res.json()1314if body["ok"]:15 print(body["data"])16else:17 print(body["error"]["code"], body["error"]["message"])1const url = new URL("https://api.socialscrape.dev/v1/threads/profile/posts");2url.search = new URLSearchParams({3 "handle": "zuck"4}).toString();5const res = await fetch(url, {6 headers: { "x-api-key": process.env.SOCIALSCRAPE_API_KEY! },7});8const body = await res.json();910if (body.ok) {11 console.log(body.data);12} else {13 console.error(body.error.code, body.error.message);14}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 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.
queryis required.keywordandqare aliases. You get one page of about 15 to 20 results, and no further pages. The reference says there is no paging. start_dateandend_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.
1curl -G "https://api.socialscrape.dev/v1/threads/search" \2 -H "x-api-key: $SOCIALSCRAPE_API_KEY" \3 --data-urlencode "query=basketball"1import os2import requests34params = {5 "query": "basketball",6}7res = requests.get(8 "https://api.socialscrape.dev/v1/threads/search",9 headers={"x-api-key": os.environ["SOCIALSCRAPE_API_KEY"]},10 params=params,11)12body = res.json()1314if body["ok"]:15 print(body["data"])16else:17 print(body["error"]["code"], body["error"]["message"])1const url = new URL("https://api.socialscrape.dev/v1/threads/search");2url.search = new URLSearchParams({3 "query": "basketball"4}).toString();5const res = await fetch(url, {6 headers: { "x-api-key": process.env.SOCIALSCRAPE_API_KEY! },7});8const body = await res.json();910if (body.ok) {11 console.log(body.data);12} else {13 console.error(body.error.code, body.error.message);14}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 that 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 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. Packs and plans have no rate limits; free accounts have a lower one. See rate limits and errors.
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.