# X Tweet Scraper (`ikdem/x-tweet-scraper`) Actor

Extract public tweets from X (Twitter) by username or tweet ID. No official API key required.

- **URL**: https://apify.com/ikdem/x-tweet-scraper.md
- **Developed by:** [Ikdem Ben Mbarek](https://apify.com/ikdem) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## X Tweet Scraper

Scrapes public tweets from [X](https://x.com) (Twitter) without an official API key and without a browser. Give it one or more handles, tweet IDs, or both. At least one of `fromUsers` or `tweetIds` is required.

It reads author timelines and hydrates tweets by ID, then writes each match to the dataset. Search is not supported — X’s search GraphQL is auth-walled for guest traffic, so `searchTerms` is rejected.

### Features

- Timelines from public profiles (`fromUsers`)
- Direct lookup by tweet ID or `/status/` URL (`tweetIds`)
- Date, language, hashtag, engagement, media, and verified filters
- Replies and retweets are off unless you turn them on
- Optional [Apify Proxy](https://docs.apify.com/platform/proxy) — pick the groups yourself
- Free runs stop at 10 tweets

### Input

Fill in the Console form or pass JSON. You need at least one of `fromUsers` or `tweetIds`.

```json
{
    "fromUsers": ["apify"],
    "tweetIds": ["https://x.com/apify/status/2087572956683567110"],
    "maxResults": 50,
    "language": "en",
    "since": "2025-01-01",
    "includeReplies": false,
    "includeRetweets": false,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
}
```

| Field | Notes |
| --- | --- |
| `fromUsers` | Handles, with or without `@`. |
| `tweetIds` | Numeric IDs or status URLs. Looked up on their own — they do not have to belong to `fromUsers`. Missing or private tweets are skipped. |
| `maxResults` | How many matching tweets to emit. Default `20`. Free users still cap at 10. |
| `hashtags` | Tweet must contain **all** of them. |
| `since` / `until` | Inclusive UTC dates (`YYYY-MM-DD`). |
| `language` | ISO 639-1, e.g. `en`. |
| `minLikes` / `minRetweets` / `minReplies` | Engagement floors. A missing count fails the filter. |
| `onlyVerified` | Verified authors only. |
| `mediaType` | `any`, `text_only`, `images`, `video`, or `links`. |
| `includeReplies` / `includeRetweets` | Default `false`. |
| `proxyConfiguration` | Standard Apify proxy object. Omit it (or set `useApifyProxy: false`) to go out directly. Optionally set `apifyProxyGroups`; the Actor does not pick a group for you. |

If you set several filters, a tweet has to pass all of them.

### Output

Each dataset item looks like this:

```json
{
    "id": "2085359738187854143",
    "url": "https://x.com/apify/status/2085359738187854143",
    "text": "$76 a month turned two competitor posts into 172 warm leads.",
    "lang": "en",
    "createdAt": "2026-08-06T13:38:00.000Z",
    "conversationId": "2085359738187854143",
    "isReply": false,
    "isRetweet": false,
    "isQuote": true,
    "inReplyToId": null,
    "quotedTweetId": "2084542540099834326",
    "author": {
        "id": "3510729917",
        "username": "apify",
        "name": "Apify",
        "verified": false,
        "followers": 12159,
        "following": 296
    },
    "metrics": {
        "likes": 6,
        "retweets": 2,
        "replies": 2,
        "quotes": 0,
        "bookmarks": 2,
        "views": 627
    },
    "entities": {
        "hashtags": [],
        "mentions": ["MannPriteshh", "InstantlyAI"],
        "urls": [],
        "media": []
    },
    "source": "Twitter Web App",
    "scrapedAt": "2026-08-19T19:16:23.448Z"
}
```

Fields that X does not provide are `null`, not omitted. Download the dataset as JSON, CSV, or Excel from the run.

The key-value store also gets an `OUTPUT` record with `pushed`, `cap`, `limited`, and `rateLimited`.

### Limits

- Public tweets only. Protected accounts and deleted IDs are skipped.
- No keyword / hashtag search. Use a user’s timeline plus filters, or pass IDs.
- Free tier: at most 10 tweets, even when `maxResults` is higher. The cap applies to emitted matching tweets, not upstream page size, so filtering can require extra pages before those 10 are filled.

For longer runs on the platform, consider enabling Apify Proxy and choose the proxy groups that fit the run.

### Retries and rate limits

Guest traffic is rate-limited. Each request gets up to 5 attempts:

- `429` and `5xx` retry with exponential backoff and jitter. `Retry-After` is honored on `429`. If a proxy is configured, the session/IP rotates before the retry; the guest token stays.
- `403` mints a new guest token and a new proxy session, then retries.
- `400` is not retried.

If the retry budget runs out after some tweets have already been saved, those tweets stay in the dataset and `rateLimited: true` is recorded. If nothing was saved yet, the run fails.

### How to run

On Apify, open the Actor, set the input, and hit Start.

Locally:

```bash
npm install
apify run
```

Input comes from `storage/key_value_stores/default/INPUT.json`. Results land in `storage/datasets/default/`.

Paid entitlement uses `ENTITLEMENTS_TOKEN` and `ENTITLEMENTS_STORE_ID` (plus the Apify user id). If those are missing, the run is treated as free.

```bash
apify login
apify push
```

# Actor input Schema

## `fromUsers` (type: `array`):

Public X handles to scrape. At least one of fromUsers or tweetIds is required. Leading @ is optional.

## `tweetIds` (type: `array`):

Numeric tweet ids or x.com /status/ URLs. Missing or private tweets are skipped.

## `maxResults` (type: `integer`):

Maximum matching tweets to emit. Free-tier runs are capped at 10 at emit time; this value is not rewritten.

## `hashtags` (type: `array`):

Keep tweets that contain every listed hashtag. Leading # is optional. Unspecified means no hashtag constraint.

## `since` (type: `string`):

Inclusive start of the createdAt window in UTC (YYYY-MM-DD).

## `until` (type: `string`):

Inclusive end of the createdAt window in UTC (YYYY-MM-DD).

## `language` (type: `string`):

ISO 639-1 code, e.g. en. Unspecified means any language.

## `minLikes` (type: `integer`):

Minimum like count. Tweets with a missing like count fail this filter.

## `minRetweets` (type: `integer`):

Minimum retweet count. Tweets with a missing retweet count fail this filter.

## `minReplies` (type: `integer`):

Minimum reply count. Tweets with a missing reply count fail this filter.

## `onlyVerified` (type: `boolean`):

Keep only tweets from verified authors.

## `mediaType` (type: `string`):

Restrict tweets by attached media or links.

## `includeReplies` (type: `boolean`):

Keep reply tweets. Default is false.

## `includeRetweets` (type: `boolean`):

Keep retweets. Default is false.

## `proxyConfiguration` (type: `object`):

Optional Apify Proxy for GraphQL requests. Choose groups here (residential vs datacenter); the Actor does not force a group. Leave unused for a direct connection.

## Actor input object example

```json
{
  "fromUsers": [
    "apify"
  ],
  "maxResults": 20,
  "onlyVerified": false,
  "mediaType": "any",
  "includeReplies": false,
  "includeRetweets": false
}
```

# Actor output Schema

## `tweets` (type: `string`):

Normalized tweets in the default dataset.

## `summary` (type: `string`):

OUTPUT record with pushed, cap, limited, and rateLimited.

# 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 = {
    "fromUsers": [
        "apify"
    ],
    "maxResults": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("ikdem/x-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 = {
    "fromUsers": ["apify"],
    "maxResults": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("ikdem/x-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 '{
  "fromUsers": [
    "apify"
  ],
  "maxResults": 20
}' |
apify call ikdem/x-tweet-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ikdem/x-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/YgZbuiCiLcwwyAzy1/builds/LqxFMABVn1LGgD957/openapi.json
