Install
openclaw skills install @scavio-ai/scavio-threadsRead Threads profiles, a user's posts and replies, a single post, its comment tree, and search Threads people as structured JSON. 6 endpoints, 2 credits by user_id and 4 by username. People search only, there is no content search.
openclaw skills install @scavio-ai/scavio-threadsRead a Threads profile, page a user's posts and replies, fetch a single post by id or URL, walk its comment tree, and search Threads for people. All endpoints return structured JSON.
Two things to know before you start: user_id costs half what a username costs, and Threads has no content search — only people search.
Use this skill when the user asks to:
Do NOT reach for this skill to search Threads for a topic, keyword or hashtag. That capability does not exist. See Guardrails.
Get a free API key at https://scavio.dev (50 free credits to get started, no card required):
export SCAVIO_API_KEY=sk_live_your_key
Every request is a POST with a JSON body and:
Authorization: Bearer $SCAVIO_API_KEY
Base URL: https://api.scavio.dev.
| Endpoint | Credits | Description |
|---|---|---|
POST /api/v1/threads/profile | 2 by user_id, 4 by username | Profile details for a Threads user |
POST /api/v1/threads/user/posts | 2 by user_id, 4 by username | A user's posts, cursor-paginated |
POST /api/v1/threads/user/replies | 2 by user_id, 4 by username | A user's replies, cursor-paginated |
POST /api/v1/threads/post | 2 | A single post by id or threads.net URL |
POST /api/v1/threads/post/comments | 2 | Replies to a post, cursor-paginated |
POST /api/v1/threads/search/users | 2 | Threads profiles matching a name or handle |
Cost is a function of the request body, not a constant.
user_id: 2 credits. This is the cheap path.username: 4 credits. A handle costs double.The reason is upstream: the provider's username lookup is dead, so passing a handle forces a second upstream call to resolve it to an id first. You pay for both.
Only /profile, /user/posts and /user/replies accept a username at all, so only those three can cost 4. /post, /post/comments and /search/users are always 2.
Resolve once, reuse the id. Call /profile or /search/users once with the handle, keep the user_id, and address everything after that by id. A 20-page crawl by handle costs 80 credits; by id it costs 40.
/threads/search/users with the name or handle (2 credits), or /threads/profile with username (4 credits). Keep the user_id you get back./threads/profile with user_id (2 credits)./threads/user/posts and /threads/user/replies with user_id. Both return next_cursor; pass it back as cursor for the next page and stop when it is null./threads/post with post_id or a threads.net url./threads/post/comments with post_id. It is cursor-paginated the same way. Note this endpoint takes post_id only, never a username.profile, post and search/users do not paginate. user/posts, user/replies and post/comments are cursor-paginated.
/profile)username or user_id is required.
| Parameter | Type | Default | Description |
|---|---|---|---|
user_id | string | one of | Numeric id, e.g. 63625256886. The 2-credit path |
username | string | one of | Handle without the @, e.g. natgeo (1-60 chars). Costs 2 extra credits |
/user/posts) and user replies (/user/replies)username or user_id is required.
| Parameter | Type | Default | Description |
|---|---|---|---|
user_id | string | one of | Numeric id. The 2-credit path |
username | string | one of | Handle without the @ (1-60 chars). Costs 2 extra credits |
cursor | string | -- | Pagination cursor from a prior next_cursor |
/post)post_id or url is required.
| Parameter | Type | Default | Description |
|---|---|---|---|
post_id | string | one of | Post id, e.g. 3349029093483693129 |
url | string | one of | A threads.net post URL |
/post/comments)| Parameter | Type | Default | Description |
|---|---|---|---|
post_id | string | required | Post id. This endpoint does NOT accept a username or a URL |
cursor | string | -- | Pagination cursor from a prior next_cursor |
/search/users)| Parameter | Type | Default | Description |
|---|---|---|---|
query | string | required | Name or handle to match (1-200 chars) |
import os, requests
BASE = "https://api.scavio.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}"}
# 1. Resolve the handle ONCE (2 credits), then work by id
found = requests.post(f"{BASE}/api/v1/threads/search/users", headers=HEADERS,
json={"query": "national geographic"}).json()
user_id = "63625256886" # taken from the search result, then reused
# 2. Profile by id: 2 credits. By username it would be 4.
profile = requests.post(f"{BASE}/api/v1/threads/profile", headers=HEADERS,
json={"user_id": user_id}).json()
# 3. Page their posts by id (2 credits per page)
page = requests.post(f"{BASE}/api/v1/threads/user/posts", headers=HEADERS,
json={"user_id": user_id}).json()
cursor = page["data"]["next_cursor"]
if cursor:
page2 = requests.post(f"{BASE}/api/v1/threads/user/posts", headers=HEADERS,
json={"user_id": user_id, "cursor": cursor}).json()
# 4. Their replies, same pattern
replies = requests.post(f"{BASE}/api/v1/threads/user/replies", headers=HEADERS,
json={"user_id": user_id}).json()
# 5. A single post, then its comment tree (post_id only, never a handle)
post = requests.post(f"{BASE}/api/v1/threads/post", headers=HEADERS,
json={"post_id": "3349029093483693129"}).json()
comments = requests.post(f"{BASE}/api/v1/threads/post/comments", headers=HEADERS,
json={"post_id": "3349029093483693129"}).json()
Every response uses the envelope { data, response_time, credits_used, credits_remaining }.
next_cursor. A null next_cursor means there are no more pages.Read credits_used on the response rather than assuming a cost. It will read 4 whenever you addressed a user-keyed endpoint by handle.
search_top and search_recent fail on every attempt. /search/users is people search and nothing else. If the user asks to search Threads posts for a term, tell them plainly that Threads does not expose this, and offer the alternative of pulling a known account's posts with /user/posts and filtering client-side. Never imply a content search exists.user_id over username everywhere. A handle doubles the cost of /profile, /user/posts and /user/replies, from 2 credits to 4. Resolve the handle once and reuse the id for the rest of the session.username and user_id together on the same request. Conflicting identifiers are rejected with a 422./post/comments takes post_id only. It has no username form and no URL form.Threads uses different error codes from the retail endpoints. There is no 400 and no 503 here.
422 means a missing or conflicting identifier: no username and no user_id, or both at once. Send exactly one.404 means no matching user was found. Check the handle spelling, or resolve it through /search/users first.401 means the API key is invalid or missing. Check SCAVIO_API_KEY.429 means a rate or usage limit was exceeded. Wait before retrying. See https://scavio.dev/docs/rate-limits.502 means the upstream errored. Wait a few seconds and retry once.next_cursor, that is the end of the data, not a failure. Stop paging.SCAVIO_API_KEY is not set, prompt the user to export it before continuing.