The Cheapest X (Twitter) API for Developers

One tweet by id or URL. One public profile by handle. From $0.10 per 1,000 calls, not per item.

Try for Free See the Rate Card
2 endpoints Lookup only 10 Free API Calls No monthly plan

Two endpoints, one key, one flat charge per call. A profile call returns the account record and its recent posts for that single charge, where per-item vendors bill each post in it. Cheapest published rate on both, checked against every rival on 2026-09-09.

Run a live X lookup

No signup and no key. The box sends a real request and prints exactly what the endpoint returns.

Two Lookups. That Is the Whole Product.

If your job is on the right-hand list, this API will not do it and no parameter will change that. Better you know now.

What it does

  • One post by id or URL. Text, language, author, engagement, media, hashtags, mentions, links, a quoted post and a reply parent.
  • One public profile by handle or URL. Name, bio, avatar, website, join date, and follower, following and post counts.
  • The recent posts that profile shows publicly, each with its id and URL, so you can look any of them up in full afterwards.
  • Engagement on those recent posts, with hydrate=true — favorites, replies and media, on each one.
  • Either endpoint as a background job. Up to 200 lookups in one submission, collected by polling, webhook or a single downloadable file.

What it does not do

  • No post search. You cannot pass a keyword, a hashtag or a date range and get matching posts back. There is no search parameter to find.
  • No full timelines. A profile returns the recent posts its public page shows — a handful, not an account’s history and not a paged feed.
  • No followers or following list. You get the counts. You never get the names behind them, at any price or any tier.
  • No retweet, quote, bookmark or view counts. Favorites and replies are the only two engagement numbers in this record, at every tier.

Why publish the limits this loudly? Because the alternative is you paying to discover them. Several vendors in the comparison table further down sell “X data” as one undifferentiated thing, and search, timelines and follower graphs are a materially different product from a lookup — different cost, different reliability, different legal posture. We sell the lookup. If you need the other one, buy it from someone who actually has it: our social API index lists what we do cover.

Two X Endpoints, One API Key

A tweet call takes the tier discounts. A profile call is one flat rate at every tier, and hydrate=true is the only thing that moves it.

Profile

Public profile

$0.10
/1K calls, flat at every tier
  • GET /api/x/profile by handle or url
  • Name, bio, avatar, website, join date
  • counts.followers, counts.following, counts.posts
  • recent_posts[] with ids and URLs; hydrate=true adds engagement and media
Default$0.10/1K calls
Growth$0.10/1K calls
Scale$0.10/1K calls
What hydrate adds →

The three rates, in full

There is no depth parameter here and no per-item component. One call is one charge, and the only thing that changes the charge is whether you asked a profile call to hydrate its recent posts.

Tweet
$0.10
/1K · $0.09 Growth · $0.07 Scale
Profile
$0.10
/1K · flat at every tier
Profile + hydrate
$0.15
/1K · 1.5× base, flat at every tier

The profile call carries no tier discount, and we would rather say so than print one that does not exist. Growth takes 10% off the tweet call and Scale takes 30%, so $0.10 becomes $0.09 and $0.07. The profile call stays at $0.10 on all three tiers, and a hydrated profile call stays at $0.15. It is the one rate on this page that a bigger deposit does not move — and at $0.10 it is already under every rival’s per-profile rate, so there is not much left to discount.

hydrate=true is a 1.5× multiplier on the profile call, not a second endpoint. Without it, each entry in recent_posts carries id, url, a best-effort copy of the post text and created_at. With it, each entry also carries counts — favorites and replies — plus any media, and the text is replaced with the exact post text. If you pay for hydration and not one recent post can be enriched, the premium is refunded automatically and you effectively pay the $0.10 base. A free-tier call spends exactly one of your 10 free calls whether you hydrate or not.

A background job costs exactly the same per lookup. Submitting the same two endpoints through the job form does not add a bulk premium and does not earn a bulk discount: every item in a job bills at the rate above for its tier, and hydrate=true carries the same 1.5× there as it does synchronously, refunded on the same terms when not one recent post could be enriched. X has no depth parameter, so a job buys you a bigger time budget and one submission instead of a thousand round trips — never more records per lookup.

A 404 is charged; a 503 is refunded. A deleted post, a suspended account or a handle nobody ever took is an answer, and the work to establish it was done, so it bills. Anything that is our problem rather than the resource’s comes back 503 Temporarily unavailable and is refunded in full. There is no third case and no partial charge.

One call, one charge, whole record. A profile call returns the account and its recent posts for a single charge, where per-item vendors bill every post in it. Growth and Scale are earned by a single $100 and $500 deposit; both are one-time and never downgrade. Every rate here also appears on the pricing page.

A Post or a Profile in One Request

One header, one GET. Both endpoints take an identifier or a URL, and both return a single record with every key always present.

cURL — tweet and profile
# A single post by its numeric id
curl "https://apiserpent.com/api/x/tweet?id=1234567890123456789" \
  -H "X-API-Key: YOUR_API_KEY"

# The same post by permalink — the id is pulled out of the URL for you
curl "https://apiserpent.com/api/x/tweet?url=https://x.com/example/status/1234567890123456789" \
  -H "X-API-Key: YOUR_API_KEY"

# A public profile. hydrate=true adds engagement + media to each recent post (1.5x)
curl "https://apiserpent.com/api/x/profile?handle=example&hydrate=true" \
  -H "X-API-Key: YOUR_API_KEY"
Python — a profile and its recent posts into rows
import os, requests

h = {"X-API-Key": os.environ["SERPENT_API_KEY"]}
p = {"handle": "example", "hydrate": "true"}

r = requests.get("https://apiserpent.com/api/x/profile",
                 params=p, headers=h, timeout=120)

# A 404 means the handle does not exist (or is suspended) — that is an answer
# and it is charged. A 503 is on us and is refunded. Branch on the two.
if r.status_code == 404:
    print("no such handle"); raise SystemExit

prof = r.json()["profile"]
print(prof["handle"], prof["counts"]["followers"], prof["joined"])

for post in prof["recent_posts"]:
    # counts is present only on a hydrated call — .get(), never [].
    c = post.get("counts") or {}
    print(post["id"], c.get("favorites"), c.get("replies"))
Node — one post record
const qs = new URLSearchParams({ id: "1234567890123456789" });

const res = await fetch(`https://apiserpent.com/api/x/tweet?${qs}`, {
  headers: { "X-API-Key": process.env.SERPENT_API_KEY }
});

const { tweet } = await res.json();
// Only favorites and replies exist on counts. There is no retweet, quote,
// bookmark or view count in this record — see the field list below.
console.log(tweet.author.screen_name, tweet.counts.favorites, tweet.counts.replies);
JSON Response — /api/x/tweet
{
  "success": true,
  "tweet": {
    "id": "1234567890123456789",
    "url": "https://x.com/example/status/1234567890123456789",
    "text": "shipping the rewrite today. three months, 41k lines deleted.",
    "lang": "en",
    "created_at": "2026-08-19T09:14:02.000Z",
    "possibly_sensitive": false,
    "is_edited": false,
    "author": {
      "id": "44196397",
      "name": "Example",
      "screen_name": "example",
      "verified": true,
      "profile_image_url": "https://pbs.twimg.com/profile_images/…_normal.jpg"
    },
    "counts": { "favorites": 18420, "replies": 612 },
    "media": [
      { "type": "photo", "url": "https://pbs.twimg.com/media/….jpg",
        "width": 1600, "height": 900 }
    ],
    "hashtags": [ "shipit" ],
    "mentions": [ "someoneelse" ],
    "urls": [ "https://example.com/changelog" ],
    "quoted_tweet": null,
    "reply_to": null
  }
}

Every key above is always present. A value we do not have arrives as null or an empty array — never as a missing key — so your parser never needs an existence check. media[] entries carry type, url, width and height; a video or animated GIF entry additionally carries poster, a variants[] list sorted by bitrate and duration_ms. hashtags, mentions and urls are plain string arrays — tags without the #, handles without the @, and links already expanded past the shortener. quoted_tweet is a compact { id, url, text, author } and reply_to is { screen_name, id }; both are null when the post is neither a quote nor a reply.

Every Field an X Call Returns

Named JSON fields, not HTML to select against. The list below is the whole contract — nothing outside it can reach a response.

Post record — /api/x/tweet

  • id, url, text, lang
  • created_at
  • possibly_sensitive, is_edited
  • author.id, .name, .screen_name
  • author.verified, .profile_image_url
  • counts.favorites, counts.replies
  • media[] (type, url, width, height)
  • media[] video: poster, variants[], duration_ms
  • hashtags[], mentions[], urls[]
  • quoted_tweet (id, url, text, author)
  • reply_to (screen_name, id)

Profile record — /api/x/profile

  • handle, name, bio
  • avatar_url
  • counts.followers
  • counts.following
  • counts.posts
  • joined, website
  • recent_posts[].id, .url
  • recent_posts[].text, .created_at
  • recent_posts[].counts (hydrate only)
  • recent_posts[].media (hydrate only)

Request parameters

  • id — numeric post id
  • url — a post permalink
  • handle — 1–15 chars, @ optional
  • url — a profile URL
  • hydrate — true/1/yes/on
  • x.com and twitter.com both accepted
  • /i/web/status/<id> permalinks accepted
  • id wins if id and url are both sent
  • handle wins if handle and url are both sent

Fields we deliberately do not send

  • retweet_count
  • quote_count
  • bookmark_count
  • views
  • profile.verified
  • no search parameter
  • no cursor / pagination
  • no followers[] / following[]
  • no author.bio on a post record

The fourth card is the honest one, so read it before you build. Retweets, quotes, bookmarks and view counts are not part of this record, and we do not ship them as permanent nulls so a dashboard can plot a flat zero and call it data. The same goes for verified on a profile: it is absent, because a wrong false on a verified account is worse for you than an honest omission — note that author.verified on a post record is a different field and is returned. If retweet counts or a verified flag on the profile record are load-bearing for your product, this API does not carry them and no plan tier adds them.

What is X (Twitter) data, and why teams use it

It is the record behind one post or one account: text, author, engagement, bio and counts. Four things decide what it costs you.

The official API bills per item returned

X’s own pay-per-usage rates are $0.005 per post read and $0.010 per user read, and its docs say plainly that reads are “charged per resource returned in the response” (docs.x.com, checked 2026-09-09).

So one profile plus its five recent posts is six resources: $0.010 + 5 × $0.005 = $0.035, or $35.00 per 1,000 profiles. Pay-per-usage is also capped at 3 million post reads per billing cycle.

Per-item and per-call are not the same market

Half this market quotes a price per tweet returned and half quotes a price per request. A headline like “$0.15 per 1,000 tweets” is the first kind; our $0.10 is the second.

On a one-post lookup the two units coincide, so the comparison is direct and we are simply lower. On anything that returns several records at once the units diverge fast, and the per-call price pulls further ahead — the table below shows both cases.

Engagement counts are the fragile part

We return counts.favorites and counts.replies and nothing else. Retweets, quotes, bookmarks and views are not in this record, and we do not pad the shape with permanent nulls to look complete.

That matters when the output is a report someone else acts on. A field that is always null and a field that is genuinely zero look identical in a chart, and only one of them is real.

Scope is what to check before you sign up

“X data” is sold as one thing and is really three: lookup by id or handle, keyword search, and follower graphs. They differ in cost, reliability and legal posture.

We sell the first and publish that in the second section of this page rather than in a support ticket. If a vendor’s page does not tell you which of the three you are buying, that is the question to ask before the card details.

Sources: X API pay-per-usage pricing and credits (docs.x.com), read 2026-09-09. Every competitor figure on this page was read from that vendor’s own live pricing page on the same day and is dated in the table below.

X API Pricing, Side by Side

Every figure is the vendor’s own published rate, read live on 2026-09-09. Units differ, so the table names each one before it names a price.

Provider What one unit is Cost per 1,000 — entry Cost per 1,000 — lowest published What the lowest rate requires Free tier
Serpent API Per call. One call = one post, or one profile with its recent posts. $0.10 post
$0.10 profile · $0.15 hydrated
$0.07 post
$0.10 profile · $0.15 hydrated (both flat)
A single $500 deposit, spendable as balance. No monthly plan, no daily bucket; the tier never downgrades. The profile rate does not move at all. 10 free API calls
X API (official) Per resource returned. One post read = one post; one user read = one profile. $5.00 post read
$10.00 user read
$5.00 post read
$10.00 user read
Nothing — pay-per-usage is the only published rate. Capped at 3M post reads per billing cycle. None published for reads
twitterapi.io Per returned item (one tweet, one user) $0.15 per 1,000 tweets
$0.18 per 1,000 users
$0.15 per 1,000 tweets
$0.18 per 1,000 users
Nothing — a single published rate. Minimum charge $0.00015 per API call. Not stated on the pricing page
Apify — X/Twitter actors Per result (one tweet, or one profile row) from $0.13 per 1,000 tweets (igolaizola)
$0.15 per 1,000 profiles (xquik) · $0.30 (api-ninja)
Below $0.13, rate not published An Apify Store discount, which scales with your paid Apify subscription; the discounted rate is not published as a number. The xquik profile rate is $0.15 on every Apify plan. apidojo: 5 runs/month, 10 items per run
ScrapingDog Per request (5 credits); one request returns one post $1.00 (LITE, $40/month) $0.136 $30,000 per month (1.1 billion credits) 200 credits = 40 X requests
SocialCrawl Per API call (1 credit for a standard call) £6.00 ≈ $8.12 (Starter, £15) £1.99 ≈ $2.69 (Pro) £299 per month 100 credits

Read the unit column first, because it is worth more than the price column. Rates checked against each vendor’s own live pricing page on 2026-09-09; SocialCrawl publishes in pounds and is converted at 1 GBP = 1.3539 USD (rate of 2026-09-08). We are the cheapest row here on both products, and at our entry rate rather than our deepest one. On a single post, $0.10 undercuts twitterapi.io’s $0.15 and the cheapest Apify tweet actor at $0.13 — and one post is one item either way, so that comparison needs no arithmetic to defend. On a profile, $0.10 undercuts Apify’s $0.15 per delivered profile and twitterapi.io’s $0.18. Then the unit widens the gap wherever one call returns several records. Take a profile plus five recent posts with engagement on each: for us that is one hydrated profile call, $0.15 per 1,000 at every tier. On twitterapi.io it is six billed items, $0.00018 + 5 × $0.00015 = $0.93 per 1,000, about ours. On the official X API it is one user read plus five post reads — $35.00 per 1,000, roughly 233× ours. Five is our own worked assumption, not a promise about how many recent posts a given profile shows. Two honest caveats, because a table nobody can rebut is worth more than a cheap one. Apify’s tweet actors discount below $0.13 on higher paid Apify plans and do not publish the resulting number, so their true floor is unknown rather than beaten; and ScrapingDog’s $0.136 per request is close to our $0.10, though it takes $30,000 a month to reach and ours takes nothing. The rate is also not the only thing we sell: no monthly plan, no daily bucket that resets at midnight, a tier earned by one deposit that never downgrades, 10 free calls to test with, and the scope limits printed in the second section of this page rather than discovered on your invoice.

What Two Lookups Are Actually Good For

Four jobs these endpoints cover well, and the shape of work they cannot do at all.

Resolving links you already have

A support inbox, a CRM note or a moderation queue full of x.com/…/status/… links turns into structured rows: text, author, engagement, media, and the links the post pointed at.

You already hold the identifier, which is exactly the case a lookup API is built for. Pass the permalink as url and the id is pulled out for you — no parsing on your side.

Qualifying an account before outreach

One profile call carries bio, website, join date and the follower, following and post counts — enough to score a handle without paying for engagement you will not read.

Add hydrate=true only for the handles that survive the first filter. That keeps the 1.5× rate on the shortlist instead of on the whole scan.

Watching a known set of accounts

Poll a fixed handle list on a schedule and diff recent_posts by id. New ids are new posts; the counts on the profile record give you follower movement over the same window.

This works because the set is known. There is no discovery here — you cannot find accounts by keyword, only re-check the ones you already named.

Embedding and archiving a cited post

Newsrooms, research tools and compliance archives need the post as it stood: exact text, author, timestamp, possibly_sensitive, is_edited, and the media URLs.

quoted_tweet and reply_to keep the thread context that makes a screenshot arguable, and a 404 tells you honestly that the post is gone rather than returning a hollow record.

Bash — a known handle list into a daily CSV
# One line per handle: date, handle, followers, posts, newest recent-post id.
# No hydrate here — this sweep only needs counts, so it bills the $0.10 rate.
while IFS= read -r handle; do
  curl -s -G "https://apiserpent.com/api/x/profile" \
      --data-urlencode "handle=$handle" \
      -H "X-API-Key: $SERPENT_API_KEY" \
    | jq -r '.profile | [.handle, (.counts.followers|tostring),
        (.counts.posts|tostring), (.recent_posts[0].id // "none")] | @csv' \
    | sed "s|^|$(date -u +%F),|" >> x-accounts.csv
done < handles.txt

That loop is the simple way, and for a handful of handles it is the right one. For a long list, submit it as a background job instead. POST /api/bulk/jobs takes 1 to 200 items in one submission with endpoint set to x_tweet or x_profile, each item carrying the same parameters as the synchronous call, and the results come back by polling, by signed webhook, or as one downloadable JSONL, JSON or CSV file. A job holds no client socket, so it runs on a far larger time budget than a single request — but X has no depth parameter, so what a job buys you is the bigger budget and one submission instead of a thousand round trips, not more records per lookup. Billing is unchanged: every item costs the published per-call rate for your tier, and hydrate=true is the same 1.5× on a job as it is here.

X (Twitter) API Questions

Yes. /api/x/tweet and /api/x/profile are both served in production and billed at the rates on this page. Everything here is the contract they answer with: the field lists, the parameters, the scope limits and the rates. Both take an identifier or a URL, both return one record with every key always present, and both can be submitted as background jobs as well as called synchronously. A signed-up account gets 10 free API calls to try them with, shared across every free-eligible endpoint; there is no card and no commitment.
Three things, and no parameter changes any of them. There is no post search — you cannot pass a keyword and get matching posts. There is no full timeline — a profile returns the recent posts its public page shows, not an account’s history. There is no followers or following list — you get the counts, never the names. Batch work is not on that list: both endpoints can be submitted as background jobs, up to 200 lookups per submission. But if your job needs search, whole timelines or the follower graph, this is the wrong API and we would rather you found that out here than after signing up.
Per call, three rates. A tweet call is $0.10 per 1,000 calls on Default, $0.09 on Growth and $0.07 on Scale. A profile call is $0.10 per 1,000 calls and is flat at every tier — there is no Growth or Scale discount on it, and we would rather say that plainly than print a discount that does not exist. Adding hydrate=true to a profile call costs 1.5 times the base, so $0.15 per 1,000 calls, also flat at every tier. Growth unlocks at a single $100 deposit and Scale at $500; both are one-time and neither ever downgrades.
Engagement and media on each recent post. Without it, every entry in recent_posts carries id, url, a best-effort copy of the post text, and created_at. With it, each entry additionally carries counts — favorites and replies — and any media on that post, and the text is replaced with the exact post text. It costs 1.5 times the base profile rate, so $0.15 per 1,000 calls. If you paid for hydration and not one recent post could be enriched, the premium is refunded automatically and you effectively pay the $0.10 base.
Yes, through the shared job form rather than a separate X route. POST /api/bulk/jobs takes 1 to 200 items per job with endpoint set to x_tweet or x_profile, and every item accepts the same parameters as the synchronous call. Results come back by polling, by signed webhook, or as one downloadable JSONL, JSON or CSV file. X has no depth parameter, so a job buys a bigger time budget and one submission instead of a thousand round trips, never more records per lookup. Billing does not change: every item costs the published per-call rate for your tier, with neither a bulk premium nor a bulk discount, and hydrate=true is the same 1.5 times multiplier on a job as it is synchronously, refunded on the same terms when not one recent post could be enriched.
Two: counts.favorites and counts.replies. Retweets, quotes, bookmarks and view counts are not part of this record, and we do not ship them as permanent nulls pretending we tried. The same discipline applies to a profile: we return followers, following and post counts, and we deliberately do not emit a verified flag, because a wrong false on a verified account is worse for you than an honest omission.
You get a 404 and it is charged. A deleted post, a suspended account, a handle that was never taken — those are answers, and the work to establish them was done. Anything that is our problem rather than the resource’s is a 503, and a 503 is always refunded. There is no third case and no partial charge: one call, one outcome, one line on your bill.
Not on either product, on every rate we could verify live on 2026-09-09, and the comparison table on this page shows the working. On a single post we are $0.10 per 1,000 calls against twitterapi.io’s $0.15 per 1,000 tweets and the cheapest Apify tweet actor’s $0.13 per 1,000 results. On a profile we are $0.10 against Apify’s $0.15 per delivered profile and twitterapi.io’s $0.18 per 1,000 users. Those vendors bill per item returned, so a profile plus five posts with engagement is six billed items for them and one call for us: $0.15 against $0.93, and against $35.00 on the official X API. Two caveats we would rather state than have you find. Apify’s tweet actors discount below $0.13 on higher paid Apify plans and do not publish the resulting number, so their true floor is unknown rather than beaten. And ScrapingDog bills per request the way we do, at $0.136 per 1,000 — close to our $0.10, though it takes $30,000 a month to reach and ours takes no commitment at all.
Neither. There is no monthly plan anywhere in Serpent’s pricing and no per-day allowance. Growth and Scale are earned by a single deposit of $100 and $500, that deposit is spendable balance rather than a fee, and the tier is permanent — it never downgrades if your usage drops. A signed-up account also gets 10 free API calls, shared across every free-eligible endpoint, with no card required. A hydrated profile call spends exactly one of those 10, not one and a half.

Start using the X API

10 free API calls, shared across every free-eligible endpoint. No card, no subscription, no monthly plan and no daily bucket. X post lookups from $0.07/1K calls.

Get your free API key

Related guides

More on working with social and creator data in code.

Social Media APIEvery social surface in the catalog, on one key. Instagram APIProfiles and recent posts in the same call, at the same price. LinkedIn APIPeople, companies, jobs and posts as structured records. TikTok APIProfile, video, hashtag, search and music, priced by depth. YouTube APIChannel, video and search data without a quota unit. Platform API vs Public Data APIWhich route fits which job, and what each one really costs. SERP API Pricing 2026Six providers, real per-1,000 costs, side by side. PricingEvery published rate, and the two one-time deposit tiers.