Skip to content
ScrapeField

Blog · Guides ·

Every post from an X account, without the X API

An account’s posts come back newest first, about 20 a call, with a cursor for older ones. A short loop reads back to any date, at a few cents per 1,000 posts, with no X developer account.

ScrapeField

X’s own API now bills every read: $0.005 a post, so 1,000 posts cost $5.00 (the comparison has its rates). This guide reads an account’s public posts by its username instead, back to a date you choose, for a few cents per 1,000. You need a key from us and nothing from X.

One page

GET /v1/x/posts takes the account’s username, or the URL of its profile. It answers with its newest posts, about 20, and a cursor for the older ones:

curl -H "Authorization: Bearer $SCRAPEFIELD_KEY" \
  "https://api.scrapefield.com/v1/x/posts?username=Ferrari"

Each post has the names X’s API v2 uses: id, text, created_at, lang, referenced_tweets for a reply or a quote, and public_metrics with like_count, retweet_count, reply_count, quote_count, bookmark_count and impression_count, the views. The author is inside each post, so there is no second lookup. Every field is in the posts reference.

A call costs 13 credits, from our cache or not, and a call that fails costs nothing. At about 20 posts a call, that is about $0.51 per 1,000 posts on the smallest pack and $0.29 on the largest.

Back to a date

Pass meta.next_cursor back as cursor, and stop at the first post older than the date you want, or when the cursor is null. In TypeScript:

const base = 'https://api.scrapefield.com/v1/x/posts';
const headers = { Authorization: `Bearer ${process.env.SCRAPEFIELD_KEY}` };
const since = '2026-09-01T00:00:00Z';
const posts = [];
let cursor: string | null = null;
read: do {
  const q = new URLSearchParams({ username: 'Ferrari' });
  if (cursor) q.set('cursor', cursor);
  const res = await fetch(`${base}?${q}`, { headers });
  const body = await res.json();
  if (!res.ok) throw new Error(body.error.message);
  for (const post of body.data) {
    if (post.created_at && post.created_at < since) break read;
    posts.push(post);
  }
  cursor = body.meta.next_cursor;
} while (cursor);

And in Python:

import os, requests

base = "https://api.scrapefield.com/v1/x/posts"
headers = {"Authorization": f"Bearer {os.environ['SCRAPEFIELD_KEY']}"}
since = "2026-09-01T00:00:00Z"
posts, cursor, done = [], None, False
while not done:
    params = {"username": "Ferrari"}
    if cursor:
        params["cursor"] = cursor
    body = requests.get(base, params=params, headers=headers).json()
    if "error" in body:
        raise RuntimeError(body["error"]["message"])
    for post in body["data"]:
        if post["created_at"] and post["created_at"] < since:
            done = True
            break
        posts.append(post)
    cursor = body["meta"]["next_cursor"]
    done = done or cursor is None

created_at is an ISO 8601 time in UTC, so comparing it as text sorts it correctly; it is null on the rare post X gives no time for, which the loop keeps. A cursor lasts as long as the answer it belongs to is cached; run the loop in one go.

What did well

With the posts in hand, rank them by public_metrics.impression_count to see what people actually saw, or by public_metrics.like_count divided by views for what they liked. A post with an entry in referenced_tweets of type replied_to is a reply; leave those out to keep the account’s own posts.

A window, not a timeline

For posts from a range of dates, search can be cheaper than paging back: X’s own operators work in query, so from:Ferrari since:2026-09-01 until:2026-09-08 returns that week only. A search call costs 13 credits for about 20 posts. Page the timeline when you want everything recent; search when you want one stretch from long ago.

Every list pages the same way, from a post’s replies to an account’s followers (how lists work). For a hundred accounts, run the calls side by side, up to your limit of calls at once.