---
name: threads
description: "Scrape public Threads posts by keyword search, profile or post URL: text, author, likes, replies, reposts, images, videos, links, mentions, profile details and the first page of replies. No login, no browser. Use when the user wants public posts from Threads (Meta): what people say about a keyword, hashtag or brand, the latest posts and follower count of a profile, or a post with its replies. Accepts usernames, profile links, post links and /t/ short links. Calls the Pullwire API with the key in PULLWIRE_API_KEY; costs 1 credit per threads post."
metadata:
  openclaw:
    requires:
      env:
        - PULLWIRE_API_KEY
      bins:
        - curl
    primaryEnv: PULLWIRE_API_KEY
    homepage: https://pullwire.dev/tools/threads
---

# Threads Scraper

Scrape public Threads posts by keyword search, profile or post URL: text, author, likes, replies, reposts, images, videos, links, mentions, profile details and the first page of replies. No login, no browser. The data comes from the Pullwire API: you send the input as JSON and get structured JSON back. Only public data.

## Setup (once)

The API key must be in the environment variable `PULLWIRE_API_KEY`. Keys start with `af_live_`. Check it without printing it:

```bash
test -n "$PULLWIRE_API_KEY" && echo "key is set" || echo "PULLWIRE_API_KEY is missing"
```

If it is missing, stop and tell the user: sign up at https://pullwire.dev/login (new accounts get 2,000 free credits), create a key at https://pullwire.dev/app/keys, then run `export PULLWIRE_API_KEY="af_live_..."` in the shell the agent uses. Never print the key, write it into a file or repeat it in the chat.

## Run it

```bash
curl -sS -X POST "https://pullwire.dev/api/v1/tools/threads/runs" \
  -H "x-api-key: $PULLWIRE_API_KEY" \
  -H "content-type: application/json" \
  -d '{"searchQueries":["coffee"],"maxResults":3}'
```

The request waits up to 25 seconds and answers with `{"run": {...}, "results": [...]}`. Check `run.status` (`succeeded` or `failed`) and `run.error`: when it is set, the run stopped early and `results` is partial (everything in it was charged). `run.creditsUsed` is what the run cost. For big answers add `-o result.json` and read only the fields you need.

Good to know:

- Put every keyword, profile and post into one run (`searchQueries`, `usernames`, `postUrls`, up to 100 each) instead of one run per item.
- Threads shows only the first page to visitors without an account: about 20 posts per search (Top order, no "Recent"), the latest few threads per profile and about 10 replies per post. `sourceHasMore` is `true` when Threads has more; say so instead of presenting the list as complete.
- Replies come back as their own results with `isReply` and `replyToCode`; set `includeReplies` to `false` when only the post itself is needed.
- A search with no results, an unknown or private profile and a broken post link return nothing and cost nothing.

## Input

The JSON body is the tool input. Fields:

| Field | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `searchQueries` | string[] | no | `[]` | Search keywords: Keywords or #hashtags to search on Threads. Each search returns the top ~20 matching threads (what Threads shows without an account). |
| `usernames` | string[] | no | `[]` | Profiles: Usernames or profile URLs. Returns the profile details and its latest public threads (Threads shows ~5 without an account). |
| `postUrls` | string[] | no | `[]` | Post URLs: Links to single Threads posts. Returns the post and, if enabled, its replies. |
| `includeReplies` | boolean | no | `true` | Include replies: For post URLs: also return the author’s follow-up posts and the first page of replies (~10, what Threads shows without an account). |
| `maxResults` | integer | no |  | Max results: Stop after this many posts in total. Empty: no extra cap. |

Without `maxResults` a run returns at most 20 results; the maximum is 200 per run. Credits for `maxResults` results are reserved before the run starts and the unused part is given back, so ask for what you need.

## Output

Each item in `results` has these fields:

| Field | Type | Always there |
| --- | --- | --- |
| `id` | string | yes |
| `code` | string | yes |
| `url` | string | yes |
| `text` | string | yes |
| `createdAt` | string | yes |
| `author` | object | yes |
| `likeCount` | integer | no |
| `replyCount` | integer | no |
| `repostCount` | integer | no |
| `quoteCount` | integer | no |
| `mediaType` | "text" \| "image" \| "video" \| "carousel" \| "audio" \| "other" | yes |
| `images` | string[] | yes |
| `videos` | string[] | yes |
| `links` | string[] | yes |
| `mentions` | string[] | yes |
| `topic` | string | no |
| `isReply` | boolean | yes |
| `replyToUsername` | string | no |
| `replyToCode` | string | no |
| `isPinned` | boolean | no |
| `profile` | object | no |
| `source` | "search" \| "profile" \| "post" | yes |
| `sourceInput` | string | yes |
| `position` | integer | yes |
| `sourceHasMore` | boolean | yes |
| `scrapedAt` | string | yes |

## Cost

1 credit per threads post. One post or reply returned from a search, profile or post URL. Inputs with no posts are not charged. Check the balance with:

```bash
curl -sS "https://pullwire.dev/api/v1/me" -H "x-api-key: $PULLWIRE_API_KEY"
```

The answer has `balance` in credits. Tell the user what a run cost when it was more than a few credits.

## Bigger jobs

Add `?async=1` to the run URL to start a background run (up to 50 seconds). It answers `202` with `run.id` right away. Poll every few seconds until `status` is `succeeded` or `failed`, then fetch the results:

```bash
curl -sS "https://pullwire.dev/api/v1/runs/$RUN_ID" -H "x-api-key: $PULLWIRE_API_KEY"
curl -sS "https://pullwire.dev/api/v1/runs/$RUN_ID/results" -H "x-api-key: $PULLWIRE_API_KEY"
```

Add `?format=csv` to the results URL when the user wants a spreadsheet.

## Errors

Errors answer `{"error": {"code": "...", "message": "..."}}`.

| Status | Code | What to do |
| --- | --- | --- |
| 400 | `INVALID_INPUT` | The message names the field. Fix the input and retry; nothing was charged. |
| 401 | `UNAUTHORIZED` | The key is missing, wrong or revoked. Ask the user to set a valid key in `PULLWIRE_API_KEY`. |
| 402 | `INSUFFICIENT_CREDITS` | The balance cannot cover the reservation. Lower `maxResults` or tell the user the balance is too low. |
| 404 | `NOT_FOUND` | Unknown tool or run ID. |
| 429 | `RATE_LIMITED` | Used up its burst of 120 requests and its 60 per minute. Wait, then retry. |
| 500 | `INTERNAL_ERROR` | Retry later. If the body has `runId`, fetch the partial results with `GET /runs/{runId}/results`. |
| 503 | `TOOL_UNAVAILABLE` | Our latest automatic checks against this site failed, so its runs are paused. Nothing was reserved or charged. Tell the user and try again later. |
| 503 | `SERVICE_UNAVAILABLE` | Retry later. |

Docs: https://pullwire.dev/docs · Tool page: https://pullwire.dev/tools/threads
