Skip to the content
Agent Tools

How to scrape X (Twitter) posts, profiles and threads

Get public X profiles, timelines, posts and threads as JSON with the X (Twitter) Scraper on Apify, from the Console or from Python and Node. Every input and output row on this page comes from running the Actor's code on 2026-10-25.

Checked 2026-10-25 by OpenKrill. We build this Actor.

Short answer

  • Open the X (Twitter) Scraper, pick a mode, type handles or post URLs, press Start.
  • It costs 0.15 USD per 1,000 results, and needs no X account or cookies.
  • It does not search X by keyword; see the comparison for tools that do.

What it reads, and what it does not

The Actor has 5 modes.

It does not search X by keyword or hashtag, and it does not list followers.

If you need search, read the comparison for the Actors and the X API endpoint that do it.

ModeWhat you getBilled as
profileOne account per handle: name, bio, location, website, join date, followers, following, post count, verified type, avatar, banner, pinned post ids.1 result per handle
timelineThe newest posts and reposts of each handle, 20 a page, with full long-post text, counts, views, links and media.1 result per post
postEach post by URL or id: text, time, language, author, replies, reposts, likes, quotes, bookmarks, views, links, media, reply and quote ids.1 result per post
threadEach post with the posts above it and what the logged-out post page shows of the author's own follow-ups.1 result per post read
repliesThe first replies X shows a reader who is not logged in.1 result per reply

Run it from Apify Console

  1. Open X (Twitter) Scraper on the Apify Store and press Try for free. A free Apify account is enough.
  2. Pick a mode under What to read.
  3. Type handles such as NASA or @golang for profile and timeline, or post URLs such as https://x.com/jack/status/20 for post, thread and replies.
  4. Set Max results. The run stops there, so it also caps the cost.
  5. Press Start, then export the dataset as JSON, CSV or Excel from the Storage tab.

Input JSON for each job

These are the exact inputs we ran on 2026-10-25.

Paste one into the JSON tab of the input form, or send it as the body of an API call.

Profiles

{
  "mode": "profile",
  "handles": [
    "NASA",
    "golang",
    "github"
  ]
}

Timeline

{
  "mode": "timeline",
  "handles": [
    "golang"
  ],
  "maxItems": 5
}

One post

{
  "mode": "post",
  "posts": [
    "https://x.com/jack/status/20"
  ]
}

Thread

{
  "mode": "thread",
  "posts": [
    "https://x.com/golang/status/2112905560152486376"
  ]
}

What the output looks like

These rows are real output of the Actor's own code, run with the inputs above on 2026-10-25 at 14:43 UTC.

We ran it outside Apify, so no charge applies to these rows; a run on Apify returns the same row shape.

A profile row

{
  "avatar": "https://pbs.twimg.com/profile_images/1321163587679784960/0ZxKlEKB_400x400.jpg",
  "banner": "https://pbs.twimg.com/profile_banners/11348282/1788992290",
  "createdAt": "2007-12-19T20:20:32Z",
  "description": "Making the seemingly impossible, possible. ✨",
  "followers": 92312227,
  "following": 115,
  "handle": "NASA",
  "id": "11348282",
  "input": "NASA",
  "location": "Pale Blue Dot",
  "name": "NASA",
  "pinnedPostIds": [],
  "posts": 74455,
  "type": "profile",
  "url": "https://x.com/NASA",
  "verified": true,
  "verifiedType": "Government",
  "website": "http://www.nasa.gov/"
}

A post row from a timeline

{
  "author": {
    "handle": "golang",
    "id": "113419064",
    "name": "Go",
    "url": "https://x.com/golang",
    "verified": false
  },
  "conversationId": "2112905560152486376",
  "counts": {
    "bookmarks": 51,
    "likes": 370,
    "quotes": 12,
    "replies": 12,
    "reposts": 61,
    "views": 64058
  },
  "createdAt": "2026-10-21T13:55:16Z",
  "id": "2112905560152486376",
  "input": "golang",
  "lang": "en",
  "links": [
    "https://blink.qualtrics.com/jfe/form/SV_ba8fa1ZcSQQdTRs/?s=x"
  ],
  "media": [],
  "quoteOfId": null,
  "replyToHandle": null,
  "replyToId": null,
  "source": "x-graphql",
  "text": "The 2026 Go Developer Survey is open! Help set Go’s priorities for next year by telling us how you use Go and where it could be better.\n\n⏱️ 10–20 minutes\n📅 Open through November 3\n\nhttps://blink.qualtrics.com/jfe/form/SV_ba8fa1ZcSQQdTRs/?s=x\n\n#golang",
  "type": "post",
  "url": "https://x.com/golang/status/2112905560152486376"
}

views is null on posts older than X's view counter, such as jack/status/20 from 2006.

source says how the row was read: x-graphql from the x.com web app's API, or x-web-page from the logged-out post page.

An error row has type: "error", a code such as unavailable or rate_limited, and is not billed.

Call it from Python or Node

Both snippets call Apify's run-sync endpoint, which starts a run, waits and returns the dataset items as JSON.

Apify answers 408 if the run takes longer than 300 seconds (Apify API docs); for big runs, start the run asynchronously and read the dataset afterwards.

Python

import json, os, urllib.request

# Standard library only. Get a token in Apify Console: Settings, API & Integrations.
url = "https://api.apify.com/v2/acts/openkrill~x-twitter-scraper/run-sync-get-dataset-items"
body = json.dumps({"mode": "timeline", "handles": ["golang"], "maxItems": 5}).encode()
req = urllib.request.Request(url, data=body, headers={
    "Content-Type": "application/json",
    "Authorization": "Bearer " + os.environ["APIFY_TOKEN"],
})
with urllib.request.urlopen(req, timeout=320) as resp:
    posts = json.load(resp)

for post in posts:
    if post["type"] == "post":
        print(post["createdAt"], post["counts"]["likes"], post["url"])

Node

// Node 18 or newer: fetch is built in, nothing to install.
const res = await fetch("https://api.apify.com/v2/acts/openkrill~x-twitter-scraper/run-sync-get-dataset-items", {
  method: "POST",
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.APIFY_TOKEN}` },
  body: JSON.stringify({ mode: "profile", handles: ["NASA", "golang", "github"] }),
});
if (!res.ok) throw new Error(`Apify answered ${res.status}`);
const profiles = await res.json();
for (const p of profiles) console.log(p.handle, p.followers, p.verifiedType);

What it costs

The Actor charges per event, read from the Apify API on 2026-10-25: 0.00015 USD per result (0.15 USD per 1,000) and 0.00005 USD per Actor start.

At the default 256 MB memory a run has 1 start event. Error items are free.

Job, in 1 runResultsCost
100 posts from 1 timeline1000.0150 USD
1,000 profiles1,0000.1500 USD
10,000 posts from 50 handles10,0001.50 USD
100,000 posts in a month100,00015.00 USD

The same 10,000 posts through the X API cost 50.00 USD at 0.005 USD per post read (X API pricing, read 2026-10-25).

If you do not want to pay anything

  • One public post: https://publish.x.com/oembed?url=https://x.com/jack/status/20 returns its text, author and date with no account.
  • A few posts or profiles: Apify's free plan credit covers thousands of results at 0.15 USD per 1,000.
  • Your own posts and mentions: the X API bills per read, and new paying accounts get 20 USD of credit after their first purchase (X API pricing).

Frequently asked questions

Can this X scraper search X by keyword or hashtag?

No. As of 2026-10-25 it reads profiles, timelines, posts, threads and first replies, but not X search. For keyword search use an Actor with a search input, or the X API recent search endpoint at 0.005 USD per post read; the comparison page lists both.

How much does it cost to scrape 1,000 tweets?

0.15 USD for the results plus one Actor start event of 0.00005 USD, so 0.15005 USD for a run that returns 1,000 posts. Error items are free. Apify's free plan credit covers it.

Do I need an X account, cookies or an API key from X?

No. The Actor reads public data as a logged-out reader, so you only need an Apify account and its API token. It never reads direct messages, protected accounts, email addresses or phone numbers.

Why does a thread come back short?

Without a login, X shows only the first view of a conversation. The thread and replies modes return what that logged-out page holds, which can be the post alone when nobody replied in the author's own thread.

Is there a free way to read one public post?

Yes. X's own oEmbed endpoint, publish.x.com/oembed?url=<post URL>, returns the text, author and date of one public post with no account and no key. It has no counts, views or media list.

Next

See how this Actor compares on price, fields and limits with the 5 most used X scrapers on Apify and the X API: X scrapers compared.

Ready to run it: X (Twitter) Scraper on Apify.