# Twitter/X Scraper - Tweets, Profiles & Trends (`viralanalyzer/twitter-scraper`) Actor

Scrape Twitter/X: tweets, user profiles, followers, hashtags, and trending topics.

- **URL**: https://apify.com/viralanalyzer/twitter-scraper.md
- **Developed by:** [viralanalyzer](https://apify.com/viralanalyzer) (community)
- **Categories:** Social media, News
- **Stats:** 120 total users, 7 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $10.00 / 1,000 tweet scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## 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

## Twitter/X Scraper 🐦

> 🔗 [View on Apify Store](https://apify.com/viralanalyzer/twitter-scraper) | 🇺🇸 English | [🇧🇷 Português](#português)

Scrape tweets, profiles, and engagement metrics from Twitter/X. Bring your own X API v2 Bearer Token for reliable access (recommended — X blocks anonymous timeline access), or use the anonymous fallback with configurable proxies.

***

### 🇺🇸 English

#### What does it do?

This actor extracts public tweets and engagement metrics from any Twitter/X profile or search query. Get real data for social media analysis, brand monitoring, and competitive intelligence.

#### Features

- **Profile scraping** — Get latest tweets from any public profile
- **Search scraping** — Find tweets matching any keyword or hashtag
- **Full metrics** — Views, likes, retweets, replies, quotes, bookmarks
- **Media extraction** — Each attachment as `{ type, url }`, from the anonymous strategies
- **Thread detection** — Identifies if a tweet is part of a thread
- **Author info** — Username, display name, follower count, verification status
- **Date filtering** — `dateFrom` / `dateTo` restrict results to a date range (UTC, both bounds inclusive), applied to each tweet's `created_at` before billing
- **Reply/Retweet filters** — Include or exclude replies and retweets

#### Capabilities & Limits

**Built here:** the anonymous GraphQL path bootstraps itself at runtime. It loads `x.com`, pulls up to 5 client-web JS bundles from `abs.twimg.com` and regex-extracts the current `queryId` for `UserByScreenName` and `UserTweets` before calling `/i/api/graphql/`. Nothing is hardcoded, so the actor survives X rotating its query IDs - and aborts that strategy rather than returning stale data if the bundles change shape.

What this actor can and cannot accept as a seed, based on the real input schema:

| Discover by | Supported | Notes |
|-------------|-----------|-------|
| Handle / username | ✅ Yes | `profiles` array (without `@`) |
| Hashtag | ⚠️ Partial | No dedicated field — pass it inside `searchQuery` (e.g. `#tech`); search mode needs the X API Bearer Token |
| Keyword / search | ⚠️ Partial | `searchQuery` works **only** with an X API v2 Bearer Token (X removed anonymous search); recent tweets only |
| URL | ❌ No | No tweet/profile URL input field in the schema |
| Subreddit | ❌ N/A | Not a Reddit actor |

**Output includes**: tweet text ✅, views ✅, likes ✅, retweets ✅, replies **count** ✅, quotes ✅, bookmarks ✅, hashtags ✅, media ✅, author (username, displayName, followers, verified) ✅, created\_at (ISO 8601) ✅, language ✅, is\_thread / is\_reply / is\_retweet ✅.

| Not in the output | | Why |
|---|---|---|
| Reply / comment **text** | ❌ | only the reply *count* is returned, never the text of individual replies |
| `thumbnail` on media items | ❌ | a media entry is `{ type, url }` — X's own media URL. No separate thumbnail or `:small` variant is emitted |
| Media on the Bearer Token path | ❌ | the official X API v2 strategy returns `media: []`. Attachments come only from the anonymous strategies (syndication / GraphQL) |

#### Input

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `profiles` | string\[] | `["elikifreitasdev"]` | Twitter/X usernames (without @) |
| `searchQuery` | string | — | Search term (alternative to profiles) |
| `maxTweets` | integer | 20 | Max tweets per profile/search (1-100) |
| `includeReplies` | boolean | false | Include reply tweets |
| `includeRetweets` | boolean | false | Include retweets |
| `dateFrom` | string | — | Inclusive lower bound on `created_at`. `YYYY-MM-DD` (= `00:00:00.000Z` UTC) or full ISO-8601 |
| `dateTo` | string | — | Inclusive upper bound on `created_at`. `YYYY-MM-DD` (= `23:59:59.999Z` UTC, whole day included) or full ISO-8601 |

#### Date range (`dateFrom` / `dateTo`) - exact behavior

Both fields are optional and independent; leaving one empty leaves that side unbounded, and leaving both empty disables the filter entirely.

- **Where it runs.** The bounds are **not** sent to any remote API or URL parameter. The actor fetches the most recent tweets each strategy exposes and then compares each tweet's `created_at` locally. A narrow or old range can therefore return **fewer items than `maxTweets`**, or nothing at all, even for an active profile.
- **Inclusive, UTC.** A tweet is kept when `dateFrom <= created_at <= dateTo`. `YYYY-MM-DD` in `dateFrom` means `00:00:00.000Z` of that day; in `dateTo` it means `23:59:59.999Z`, so `dateFrom = dateTo = 2026-01-20` returns that whole day. A full ISO-8601 timestamp is used exactly as given.
- **Billing.** The filter runs **before** the tweet is stored and before the Pay Per Event counter is incremented. **You are never charged for a tweet the date filter discards.**
- **Tweets with no date.** A few anonymous fallback paths return a tweet whose `created_at` is `null`. While any bound is set, such a tweet is **dropped** - its membership in the range cannot be verified, so it is neither returned nor charged. The run log reports how many were dropped for this reason.
- **Invalid input.** An unreadable `dateFrom` / `dateTo`, or a range where `dateFrom` is after `dateTo`, **stops the run** with a labeled `_dataQuality: "diagnostic"` item explaining the problem, and charges nothing. The filter is never silently ignored.
- **Nothing in range.** If tweets were found but all fell outside the range, the run succeeds with a diagnostic item stating how many were discarded - again with no charge.

#### Output Example

```json
{
  "tweet_id": "1891234567890123456",
  "url": "https://x.com/elikifreitasdev/status/1891234567890123456",
  "text": "🚀 Launched the new version! Performance 3x better #tech #automation",
  "author": {
    "username": "elikifreitasdev",
    "displayName": "Eliki Freitas | Dev",
    "followers": 12500,
    "verified": false
  },
  "created_at": "2026-01-20T14:30:00.000Z",
  "views": 45200,
  "likes": 892,
  "retweets": 156,
  "replies": 43,
  "quotes": 12,
  "bookmarks": 234,
  "hashtags": ["tech", "automation"],
  "media": [
    {
      "type": "image",
      "url": "https://pbs.twimg.com/media/example.jpg"
    }
  ],
  "is_thread": false,
  "is_reply": false,
  "is_retweet": false,
  "language": "en"
}
```

#### Use Cases

1. **Brand Monitoring** — Track mentions and sentiment about your brand
2. **Competitor Analysis** — Monitor competitor tweet performance
3. **Influencer Research** — Evaluate engagement rates before partnerships
4. **Trend Detection** — Discover viral topics and hashtags
5. **Content Strategy** — Analyze what tweet formats get the most engagement

#### FAQ

**Does it require a Twitter API key?**
Recommended. X blocks anonymous (guest) access to timelines, so for reliable results provide your own `twitterBearerToken` (X API v2 — your own plan/quota at https://developer.x.com). **Search/keyword mode (`searchQuery`) requires the Bearer Token** (X removed anonymous search). Without a token, the actor tries anonymous strategies (subject to blocking) and, if nothing is extracted, returns a diagnostic guide without charging PPE.

**Why did `dateFrom` / `dateTo` return fewer tweets than `maxTweets`?**
Because the range is applied locally, to the tweets the actor was able to fetch (the most recent ones a strategy exposes), not as a remote query parameter. Tweets outside the range — and, while a bound is set, tweets with no readable `created_at` — are dropped before being stored, and are **not charged**. See "Date range — exact behavior" above.

**Can it scrape protected/private accounts?**
No. Only public profiles and public tweets are accessible.

**What about rate limits?**
The actor handles rate limiting automatically with built-in delays and retries.

**How many tweets can I get per run?**
Up to 100 tweets per profile or search query per run.

***

### 🇧🇷 Português

#### O que faz?

Este actor extrai tweets públicos e métricas de engajamento de qualquer perfil ou busca no Twitter/X. Dados reais para análise de redes sociais, monitoramento de marca e inteligência competitiva.

#### Funcionalidades

- **Scraping de perfil** — Últimos tweets de qualquer perfil público
- **Scraping de busca** — Encontre tweets por palavra-chave ou hashtag
- **Métricas completas** — Visualizações, curtidas, retweets, respostas, citações, bookmarks
- **Extração de mídia** — Cada anexo como `{ type, url }`, vindo das estratégias anônimas
- **Detecção de thread** — Identifica se o tweet faz parte de uma thread
- **Info do autor** — Username, nome, seguidores, verificação
- **Filtro por data** — `dateFrom` / `dateTo` restringem os resultados a um período (UTC, ambos os limites inclusivos), aplicados ao `created_at` de cada tweet antes da cobrança
- **Filtros de resposta/retweet** — Inclua ou exclua respostas e retweets

#### Capacidades e Limites

O que este actor aceita ou não como semente, conforme o input schema real:

| Descobre por | Suportado | Observações |
|--------------|-----------|-------------|
| Handle / username | ✅ Sim | Array `profiles` (sem `@`) |
| Hashtag | ⚠️ Parcial | Sem campo dedicado — passe dentro de `searchQuery` (ex.: `#tech`); o modo de busca exige o Bearer Token da API do X |
| Palavra-chave / busca | ⚠️ Parcial | `searchQuery` funciona **apenas** com Bearer Token da API X v2 (o X removeu a busca anônima); somente tweets recentes |
| URL | ❌ Não | Não há campo de URL de tweet/perfil no schema |
| Subreddit | ❌ N/A | Não é um actor de Reddit |

**A saída inclui**: texto do tweet ✅, visualizações ✅, curtidas ✅, retweets ✅, **contagem** de respostas ✅, citações ✅, bookmarks ✅, hashtags ✅, mídia ✅, autor (username, displayName, seguidores, verificado) ✅, created\_at (ISO 8601) ✅, idioma ✅, is\_thread / is\_reply / is\_retweet ✅.

| Não sai na saída | | Motivo |
|---|---|---|
| **TEXTO** das respostas/comentários | ❌ | só a *contagem* de respostas é retornada, nunca o texto de cada resposta |
| `thumbnail` nos itens de mídia | ❌ | cada anexo é `{ type, url }` — a própria URL de mídia do X. Não há thumbnail separado nem variante `:small` |
| Mídia no caminho com Bearer Token | ❌ | a estratégia oficial da API X v2 devolve `media: []`. Os anexos vêm apenas das estratégias anônimas (syndication / GraphQL) |

#### Entrada

| Campo | Tipo | Padrão | Descrição |
|-------|------|--------|-----------|
| `profiles` | string\[] | `["elikifreitasdev"]` | Usernames do Twitter/X (sem @) |
| `searchQuery` | string | — | Termo de busca (alternativa aos perfis) |
| `maxTweets` | integer | 20 | Máx tweets por perfil/busca (1-100) |
| `includeReplies` | boolean | false | Incluir tweets de resposta |
| `includeRetweets` | boolean | false | Incluir retweets |
| `dateFrom` | string | — | Limite inferior inclusivo sobre `created_at`. `YYYY-MM-DD` (= `00:00:00.000Z` UTC) ou ISO-8601 completo |
| `dateTo` | string | — | Limite superior inclusivo sobre `created_at`. `YYYY-MM-DD` (= `23:59:59.999Z` UTC, dia inteiro incluído) ou ISO-8601 completo |

#### Intervalo de datas (`dateFrom` / `dateTo`) - comportamento exato

Os dois campos são opcionais e independentes: deixar um vazio deixa aquele lado sem limite, e deixar os dois vazios desliga o filtro.

- **Onde roda.** Os limites **não** são enviados a nenhuma API ou parâmetro de URL. O actor busca os tweets mais recentes que cada estratégia expõe e compara o `created_at` de cada um localmente. Por isso um intervalo estreito ou antigo pode devolver **menos itens que `maxTweets`**, ou nenhum, mesmo em um perfil ativo.
- **Inclusivo, em UTC.** O tweet é mantido quando `dateFrom <= created_at <= dateTo`. `YYYY-MM-DD` em `dateFrom` significa `00:00:00.000Z` daquele dia; em `dateTo` significa `23:59:59.999Z`, então `dateFrom = dateTo = 2026-01-20` devolve o dia inteiro. Um timestamp ISO-8601 completo é usado exatamente como informado.
- **Cobrança.** O filtro roda **antes** de o tweet ser gravado e antes de o contador do Pay Per Event ser incrementado. **Você nunca é cobrado por um tweet descartado pelo filtro de data.**
- **Tweets sem data.** Alguns caminhos anônimos de fallback devolvem tweet com `created_at` igual a `null`. Com qualquer limite ativo, esse tweet é **descartado** - não dá para verificar se ele pertence ao intervalo, então ele não é devolvido nem cobrado. O log do run informa quantos caíram por esse motivo.
- **Entrada inválida.** `dateFrom` / `dateTo` ilegível, ou intervalo em que `dateFrom` é posterior a `dateTo`, **interrompe o run** com um item `_dataQuality: "diagnostic"` explicando o problema, sem cobrar nada. O filtro nunca é ignorado em silêncio.
- **Nada no intervalo.** Se tweets foram encontrados mas todos ficaram fora do intervalo, o run termina com sucesso e um item de diagnóstico dizendo quantos foram descartados - também sem cobrança.

#### Exemplo de Saída

```json
{
  "tweet_id": "1891234567890123456",
  "url": "https://x.com/elikifreitasdev/status/1891234567890123456",
  "text": "🚀 Lançamos a nova versão! Performance 3x melhor #tech #automation",
  "author": {
    "username": "elikifreitasdev",
    "displayName": "Eliki Freitas | Dev",
    "followers": 12500,
    "verified": false
  },
  "created_at": "2026-01-20T14:30:00.000Z",
  "views": 45200,
  "likes": 892,
  "retweets": 156,
  "replies": 43,
  "quotes": 12,
  "bookmarks": 234,
  "hashtags": ["tech", "automation"],
  "media": [
    {
      "type": "image",
      "url": "https://pbs.twimg.com/media/example.jpg"
    }
  ],
  "is_thread": false,
  "is_reply": false,
  "is_retweet": false,
  "language": "pt"
}
```

#### Casos de Uso

1. **Monitoramento de Marca** — Acompanhe menções e sentimento sobre sua marca
2. **Análise de Concorrência** — Monitore performance de tweets de concorrentes
3. **Pesquisa de Influenciadores** — Avalie taxas de engajamento antes de parcerias
4. **Detecção de Tendências** — Descubra tópicos virais e hashtags
5. **Estratégia de Conteúdo** — Analise quais formatos de tweet geram mais engajamento

#### FAQ

**Precisa de chave de API do Twitter?**
Recomendado. O X bloqueia acesso anônimo a timelines; informe seu `twitterBearerToken` (X API v2, plano/quota próprios — https://developer.x.com) para extração confiável. Sem token, o actor tenta estratégias anônimas (sujeitas a bloqueio) e, se nada for extraído, devolve um guia de diagnóstico sem cobrar PPE.

**Por que `dateFrom` / `dateTo` devolveram menos tweets que `maxTweets`?**
Porque o intervalo é aplicado localmente, sobre os tweets que o actor conseguiu buscar (os mais recentes que a estratégia expõe), e não como parâmetro remoto de consulta. Tweets fora do intervalo — e, com um limite ativo, tweets sem `created_at` legível — são descartados antes de serem gravados, e **não são cobrados**. Veja "Intervalo de datas — comportamento exato" acima.

**Pode scraper contas protegidas/privadas?**
Não. Apenas perfis públicos e tweets públicos são acessíveis.

**E sobre limites de requisição?**
O actor gerencia rate limiting automaticamente com delays e retentativas.

**Quantos tweets posso obter por execução?**
Até 100 tweets por perfil ou busca por execução.

***

### 💰 Pricing

This actor uses **Pay Per Event (PPE)** pricing. The rate depends on your Apify plan tier:

| Plan tier | Per tweet | Per 1,000 tweets |
|-----------|-----------|------------------|
| Free      | $0.02     | $20.00 |
| Bronze    | $0.0175   | $17.50 |
| Silver    | $0.015    | $15.00 |
| Gold      | $0.01     | $10.00 |
| Platinum  | $0.0075   | $7.50  |
| Diamond   | $0.005    | $5.00  |

**You are never charged for a run that returned nothing.** When X blocks the request and no
tweet is extracted, the actor returns a labeled `_dataQuality: "diagnostic"` setup guide and
charges no PPE event. The same holds for the `dateFrom` / `dateTo` filter: tweets discarded
because they fall outside the requested range — or because they carry no readable date while a
bound is set — are dropped before the Pay Per Event counter, so they are never billed.

### 🔗 Related Actors

- [Instagram Reels & Posts Scraper](https://apify.com/viralanalyzer/instagram-reels-scraper)
- [TikTok Viral Scanner](https://apify.com/viralanalyzer/tiktok-viral-scanner)
- [Reddit Scraper](https://apify.com/viralanalyzer/reddit-scraper)
- [YouTube Fast Scraper](https://apify.com/viralanalyzer/youtube-fast-scraper)

***

### ✅ Available / Disponível — cross-actor upgrades

> The items below were **PLANNED** in earlier versions and are now **implemented and live** in this actor. They are **additive and backward-compatible**: when the new input fields are omitted and `normalizeOutput` is `false`, the output is byte-for-byte identical to before — paying users are unaffected.
>
> Os itens abaixo eram **PLANEJADOS** em versões anteriores e agora estão **implementados e disponíveis** neste actor. São **aditivos e retrocompatíveis**: quando os novos campos de input são omitidos e `normalizeOutput` é `false`, a saída é byte-a-byte idêntica à anterior — usuários pagantes não são afetados.

**EN**

- **Unified input vocabulary** (#1) ✅ — `maxResults` (alias of `maxTweets`), `seeds` + `seedType` (`handle` | `url` | `keyword`) are accepted as a cross-actor unified seed list. The existing `profiles` / `searchQuery` / `maxTweets` fields keep working exactly as before; the new ones are optional aliases.
- **Normalized `_normalized` output block** (#2) ✅ — set the optional input `normalizeOutput: true` to attach a cross-platform block to each item: `{ platform, url, author, text, views, likes, comments, shares, publishedAtISO, lang, hashtags[], engagementVelocity }`, alongside the existing raw fields. For Twitter/X, `comments` maps from `replies`, `shares` from `retweets + quotes`, `views` from impressions. Default `false`.
- **ISO date + `engagementVelocity`** (#6) ✅ — inside `_normalized`, `publishedAtISO` is the tweet date in ISO-8601 UTC, and `engagementVelocity` = `(likes + comments + shares) / hours_since_published` (or `null` when no date is available).
- **Quality flag `_dataQuality`** (#7) ✅ — successful items carry `_dataQuality: "full"` (when `normalizeOutput` is on); the recoverable no-results setup guide carries `_dataQuality: "diagnostic"` and is **not** charged via PPE.

**PT**

- **Vocabulário de input unificado** (#1) ✅ — `maxResults` (alias de `maxTweets`), `seeds` + `seedType` (`handle` | `url` | `keyword`) são aceitos como lista de sementes unificada entre actors. Os campos `profiles` / `searchQuery` / `maxTweets` continuam funcionando exatamente como antes; os novos são aliases opcionais.
- **Bloco de saída normalizado `_normalized`** (#2) ✅ — defina o input opcional `normalizeOutput: true` para anexar a cada item um bloco cross-plataforma: `{ platform, url, author, text, views, likes, comments, shares, publishedAtISO, lang, hashtags[], engagementVelocity }`, ao lado dos campos crus. No Twitter/X, `comments` vem de `replies`, `shares` de `retweets + quotes`, `views` das impressões. Padrão `false`.
- **Data ISO + `engagementVelocity`** (#6) ✅ — dentro de `_normalized`, `publishedAtISO` é a data do tweet em ISO-8601 UTC, e `engagementVelocity` = `(likes + comments + shares) / horas_desde_publicação` (ou `null` quando não há data).
- **Flag de qualidade `_dataQuality`** (#7) ✅ — itens bem-sucedidos carregam `_dataQuality: "full"` (quando `normalizeOutput` está ligado); o guia de diagnóstico recuperável (sem resultados) carrega `_dataQuality: "diagnostic"` e **não** é cobrado via PPE.

***

### 🛣️ Roadmap / Próximas melhorias (planned)

> ⚠️ The items below are **PLANNED**, not yet available. They are not current features — do not rely on them yet. Source: internal social-scrapers improvements handoff (2026-06-19).
>
> ⚠️ Os itens abaixo são **PLANEJADOS**, ainda não disponíveis. Não são funcionalidades atuais — não dependa deles ainda. Fonte: handoff interno de melhorias dos scrapers sociais (2026-06-19).

**EN**

- **Structured `_blockReason` code** (part of #7) — on a confirmed block, emit a machine-readable reason (`403 | 429 | captcha`) on the diagnostic item. Not yet implemented to avoid changing the current anti-bot/diagnostic gate; today blocks are surfaced as a labeled `_dataQuality: "diagnostic"` guide only.

**PT**

- **Código `_blockReason` estruturado** (parte do #7) — em bloqueio confirmado, emitir um motivo legível por máquina (`403 | 429 | captcha`) no item de diagnóstico. Ainda não implementado para não alterar o gate anti-bot/diagnóstico atual; hoje os bloqueios são expostos apenas como guia rotulado `_dataQuality: "diagnostic"`.

***

### 📝 Changelog

- **v1.1.0** (2026-08-27) — `dateFrom` / `dateTo` are now honored: the range is enforced in memory against each tweet's `created_at` (UTC, both bounds inclusive) before the tweet is stored and before Pay Per Event, so filtered-out tweets are never charged. Tweets with no readable date are dropped while a bound is set, and an invalid bound stops the run with a diagnostic item instead of being silently ignored.
- **v1.0.0** (2026-02-12) — Initial release: profile scraping, search, full engagement metrics, thread detection, media extraction

# Actor input Schema

## `profiles` (type: `array`):

List of Twitter/X usernames to scrape (e.g. \['nasa', 'elonmusk']). Without @. Medido 2026-09-09: o endpoint de syndication devolve 20 entries para @nasa e 99 para @elonmusk; conta inexistente ou sem tweets publicos devolve 0 e o actor emite diagnostico.

## `searchQuery` (type: `string`):

Search term across Twitter/X (alternative to profiles). Leave empty to use profiles list.

## `maxTweets` (type: `integer`):

Maximum number of tweets to extract per profile or search

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

Cross-actor alias for maxTweets. When set, it overrides maxTweets. Optional — if omitted, maxTweets (or its default) is used. Behavior is identical to maxTweets.

## `seeds` (type: `array`):

Cross-actor unified seed list. Optional. Combined with 'seedType' to choose how each entry is interpreted. If omitted/empty, the actor uses 'profiles' / 'searchQuery' exactly as before. seedType=handle → usernames (without @); seedType=url → twitter.com/x.com profile or tweet URLs (the username is extracted); seedType=keyword → search terms (requires the X API Bearer Token, like searchQuery).

## `seedType` (type: `string`):

How to interpret the 'seeds' entries. Only applies when 'seeds' is non-empty. 'handle' = Twitter/X usernames; 'url' = profile/tweet URLs (username extracted); 'keyword' = search terms (requires the X API Bearer Token).

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

Include reply tweets in results

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

Include retweets in results

## `dateFrom` (type: `string`):

Lower bound, inclusive. Keeps only tweets whose created\_at is on or after it. Accepts YYYY-MM-DD (read as 00:00:00.000Z of that day, UTC) or a full ISO-8601 timestamp. Leave empty for no lower bound. Applied in memory to the tweets the actor extracted, BEFORE billing: a tweet the filter drops is never pushed and never charged, so a narrow range can return fewer items than Max Tweets. While any date filter is set, tweets with no readable date are dropped too. An unreadable value stops the run with a diagnostic item and no charge.

## `dateTo` (type: `string`):

Upper bound, inclusive. Keeps only tweets whose created\_at is on or before it. Accepts YYYY-MM-DD (read as 23:59:59.999Z of that day, UTC, so the whole day is included) or a full ISO-8601 timestamp. Leave empty for no upper bound. Same billing rule as Date From: filtered-out tweets are never charged, and an unreadable value or a range where Date From is after Date To stops the run with a diagnostic item and no charge.

## `twitterBearerToken` (type: `string`):

Official X (Twitter) API v2 Bearer Token. When provided, the actor calls the official API first — the only reliable method, since X blocks anonymous (guest) access to timelines. Get yours at https://developer.x.com (your own X API plan/quota applies).

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

Proxy used by the anonymous scraping strategies. Default = Apify datacenter proxy (low cost). If runs return only a diagnostic item, enable RESIDENTIAL proxies here — note that residential traffic is billed as platform usage.

## `normalizeOutput` (type: `boolean`):

When true, each item additionally carries a cross-platform '\_normalized' block { platform, url, author, text, views, likes, comments, shares, publishedAtISO, lang, hashtags\[], engagementVelocity } alongside the existing raw fields. Default false — output is byte-for-byte identical to before.

## Actor input object example

```json
{
  "profiles": [
    "nasa"
  ],
  "maxTweets": 20,
  "seedType": "handle",
  "includeReplies": false,
  "includeRetweets": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "normalizeOutput": false
}
```

# Actor output Schema

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

Dataset containing all scraped results. Each item follows the dataset schema.

# 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 = {
    "profiles": [
        "nasa"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("viralanalyzer/twitter-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 = {
    "profiles": ["nasa"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("viralanalyzer/twitter-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 '{
  "profiles": [
    "nasa"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call viralanalyzer/twitter-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,viralanalyzer/twitter-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/Vaxm7fVgEyEkVeYs8/builds/3AU2oXXypPggEtxY8/openapi.json
