# Tweet Scraper | X Scraper | $0.12/1K | Profiles, Tweets (`opentwitter.com/tweet-scraper`) Actor

Look up X (Twitter) profiles, home tweets, one tweet with replies, following, and followers. $0.12 per 1,000 results. Each profile, tweet, reply, or account is one result.

- **URL**: https://apify.com/opentwitter.com/tweet-scraper.md
- **Developed by:** [Handian Geinais](https://apify.com/opentwitter.com) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.12 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Tweet Scraper | X Scraper

Look up public X (Twitter) data: a profile, home tweets, one tweet and the replies on that page, following, and followers.

Full field reference, including HTTP examples: <https://www.opentwitter.com/docs>

$0.12 per 1,000 dataset rows. Apify charges one result for each row. Leave **OpenTwitter API key** blank. This Actor uses the key stored on it.

A name, `@name`, or an `x.com` profile link all work in **Handles**. Tweet ids and user ids are strings. A short number such as `1234567890` is rejected.

### What each run accepts

| Field | Required | Values |
|---|---|---|
| What to fetch | yes | `tweets`, `profile`, `both`, `tweet`, `following`, `followers` |
| Handles | yes, except One tweet | One account per line |
| Tweet id or status URL | One tweet only | 15–20 digits, or a status URL |
| User id | no | Numeric id you already know. Skips one profile lookup |
| Count | see each section | Home tweets: 20–40, default 20. Following and followers: 1–200, default 200 |
| Concurrency | no | How many accounts at once. Default 3, maximum 8 |
| API base URL | no | Leave `https://api.opentwitter.com` |

Each successful item is one dataset row. Open Storage to see `profile`, `tweet`, or `user`. The table shows `handle`, `endpoint`, `status`, and `ok`.

A missing or suspended account is HTTP 404 and still one row:

```json
{
  "handle": "elonmusk",
  "endpoint": "profile",
  "status": 404,
  "ok": false,
  "error": "This account was not found"
}
```

A bad key, a bad parameter, no balance, or a server error is not written to the dataset and is not charged.

### Profile

One public profile. Count is ignored. One profile is 1 result. A 404 is also 1 result.

Request:

```json
{ "endpoint": "profile", "handles": ["elonmusk"] }
```

Calls `POST https://api.opentwitter.com/v1/lookup/profile` with `{ "handle": "elonmusk" }`.

Response, one row:

```json
{
  "handle": "elonmusk",
  "endpoint": "profile",
  "status": 200,
  "ok": true,
  "profile": {
    "user_id": "44196397",
    "screen_name": "elonmusk",
    "display_name": "Elon Musk",
    "bio": "",
    "location": "",
    "website": "",
    "followers_count": 0,
    "following_count": 0,
    "tweets_count": 0,
    "likes_count": 0,
    "verified": false,
    "protected": false,
    "avatar_url": "https://pbs.twimg.com/profile_images/1.jpg",
    "banner_url": "https://pbs.twimg.com/profile_banners/1.jpg",
    "created_at": "Tue Jun 02 00:00:00 +0000 2009"
  }
}
```

### Home tweets

Latest posts on the profile timeline. `count` must be 20–40. Default 20. One request to X returns at least 20 posts, so a smaller count is rejected. Each returned tweet is 1 result. Duplicate tweet ids are removed. Zero tweets, or a 404, is still 1 result.

Request:

```json
{ "endpoint": "tweets", "handles": ["elonmusk"], "count": 20 }
```

Calls `POST https://api.opentwitter.com/v1/lookup/tweets` with `{ "handle": "elonmusk", "count": 20 }`. Add `"user_id": "44196397"` when you already know it.

Response, one row per tweet:

```json
{
  "handle": "elonmusk",
  "endpoint": "tweets",
  "status": 200,
  "ok": true,
  "tweet": {
    "tweet_id": "1840000000000000002",
    "text": "tweet text",
    "created_at": "2026-09-17T10:00:00.000Z",
    "url": "https://x.com/elonmusk/status/1840000000000000002",
    "likes": 0,
    "retweets": 0,
    "replies": 0,
    "views": 0,
    "is_retweet": false,
    "is_reply": false,
    "is_quoted": false,
    "urls": []
  }
}
```

Several accounts. Default concurrency is 3, maximum 8.

```json
{ "endpoint": "tweets", "handles": ["elonmusk", "nasa"], "count": 20, "concurrency": 3 }
```

### Profile and tweets

Both in one run. Tweet count is 20–40, same floor as home tweets. The dataset is split: one profile row, then one row per tweet. Each side is billed on its own. One side can fail while the other succeeds. A protected account can still return the profile, with tweets as a 404 row.

Request:

```json
{ "endpoint": "both", "handles": ["elonmusk"], "count": 20 }
```

Calls `POST https://api.opentwitter.com/v1/lookup` with `{ "handle": "elonmusk", "count": 20 }`.

Response rows use the same `profile` object as Profile and the same `tweet` object as Home tweets. `endpoint` on those rows is `profile` or `tweets`.

### One tweet

That tweet, plus the replies on the same page. Handles and count are ignored. The main tweet and each reply are each 1 result. Duplicate ids are removed. Zero items, or a 404, is still 1 result.

Request. Keep the id a string. A status URL is accepted.

```json
{ "endpoint": "tweet", "tweetId": "1840000000000000002" }
```

```json
{ "endpoint": "tweet", "tweetId": "https://x.com/elonmusk/status/1840000000000000002" }
```

Calls `POST https://api.opentwitter.com/v1/lookup/tweet` with `{ "tweet_id": "1840000000000000002" }`.

Response, one row for the tweet and one row for each reply. The shape is the same `tweet` object as Home tweets. A reply has `"is_reply": true`.

### Following

Accounts this user follows, most recent first. `count` is 1–200, default 200. The lookup stops at that count, so the bill matches the accounts returned. Each account is 1 result. Duplicate user ids are removed. Zero accounts, or a 404, is still 1 result. X does not return the time they were followed. `account_created_at` is when that account was created.

Request:

```json
{ "endpoint": "following", "handles": ["elonmusk"], "count": 50 }
```

Calls `POST https://api.opentwitter.com/v1/lookup/following` with `{ "handle": "elonmusk", "count": 50 }`.

Response, one row per account:

```json
{
  "handle": "elonmusk",
  "endpoint": "following",
  "status": 200,
  "ok": true,
  "user": {
    "user_id": "12",
    "handle": "jack",
    "name": "jack",
    "bio": "",
    "followers_count": 0,
    "following_count": 0,
    "tweets_count": 0,
    "verified": false,
    "protected": false,
    "avatar_url": null,
    "location": "",
    "account_created_at": "Tue Mar 21 00:00:00 +0000 2006"
  }
}
```

This run does not page further than `count`. `next_cursor` from the API is not returned as its own field.

### Followers

Accounts that follow this user. Same count rule and same `user` object as Following.

Request:

```json
{ "endpoint": "followers", "handles": ["elonmusk"], "count": 50 }
```

Calls `POST https://api.opentwitter.com/v1/lookup/followers` with `{ "handle": "elonmusk", "count": 50 }`.

Response rows use `"endpoint": "followers"` and the same `user` object as Following.

### Not included

No keyword search, no lists, no date range, and no batch endpoint. More than 40 home tweets, or more than 200 follow-list accounts, needs another run. This Actor does not monitor an account over time.

# Actor input Schema

## `apiKey` (type: `string`):

Leave this blank. The Actor uses its own key. You are billed by Apify at $0.12 per 1,000 results.

## `endpoint` (type: `string`):

Home tweets: 20-40 posts, default 20. X returns at least 20 posts per request, and each post is one result. A count under 20 is rejected. Profile: name, bio, and counts. Profile and tweets: both in one run, same 20-40 tweet floor. One tweet: that tweet plus replies on the page. Following or followers: 1-200 accounts, and you are billed only for the accounts returned.

## `handles` (type: `array`):

One account per line. A name, @name, or an x.com profile link works. Not used when What to fetch is One tweet.

## `tweetId` (type: `string`):

Required only for One tweet. Paste a status URL or a 15-20 digit id. Keep it a string so the id does not lose precision.

## `userId` (type: `string`):

Optional. The numeric user id, if you already know it. Leave blank to look the account up from the handle.

## `count` (type: `integer`):

Home tweets and Profile and tweets: 20-40, default 20. One request to X returns at least 20 posts. A lower count is rejected, so you are not billed for a full page after typing a smaller number. Following and followers: 1-200. The run stops at this count and bills one result per account returned. Ignored for Profile and One tweet.

## `concurrency` (type: `integer`):

How many accounts to look up at the same time. Default 3, maximum 8.

## `apiBase` (type: `string`):

Leave this as https://api.opentwitter.com. Request and response for every lookup: https://www.opentwitter.com/docs

## Actor input object example

```json
{
  "endpoint": "tweets",
  "handles": [
    "elonmusk"
  ],
  "concurrency": 3,
  "apiBase": "https://api.opentwitter.com"
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "handles": [
        "elonmusk"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("opentwitter.com/tweet-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "handles": ["elonmusk"] }

# Run the Actor and wait for it to finish
run = client.actor("opentwitter.com/tweet-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "handles": [
    "elonmusk"
  ]
}' |
apify call opentwitter.com/tweet-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,opentwitter.com/tweet-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/a7GUv5xStqmHMBbTr/builds/w5DwVmSKAA5RJzn7Z/openapi.json
