GuidesSOCIAL APIS
Free Twitter API alternative
X's API has been pay-per-use since 6 February 2026, at $0.005 per post read and $0.015 per post created, with no free tier for new apps. To show single public posts in the browser for free, use cdn.syndication.twimg.com/tweet-result, the undocumented endpoint behind embedded posts. It only allows https://platform.twitter.com in its CORS header, so call it through the free proxy.cors.dev (no signup, GET and HEAD, about 600 requests a minute per IP); it returns one post per request, with no search or timelines.
What the X API costs in 2026
X launched pay-per-use pricing for its API on 6 February 2026, according to the X developer changelog. Under this model, you buy credits upfront and pay per request. Recently active users of the legacy free tier received a one-time $10 voucher, while Public Utility Apps keep free access. The changelog says X's Basic plan and X's Pro plan (fixed fees of $200 and $5,000 a month) remain available and that existing subscribers can opt in to pay-per-use; the pricing page itself lists only pay-per-use.
Rates from the X API pricing page are below. Owned reads cover your own posts, mentions, likes, bookmarks and followers.
| Operation | Price | Billed per |
|---|---|---|
| Post read | $0.005 | post |
| User read | $0.010 | user |
| Followers or following read | $0.010 | resource |
| Like, mute or block read | $0.001 | resource |
| Owned read | $0.001 | resource |
| Create a post | $0.015 | request |
| Create a post with a URL | $0.200 | request |
| DM interaction | $0.015 | request |
| Trends | $0.010 | request |
At $0.005 per post, reading 1,000 posts costs $5 and reading 10,000 posts costs $50. Reading the same resource again within the same 24-hour UTC window is billed once. Pay-per-use is capped at 3 million post reads per monthly billing cycle, and above that you need X's Enterprise plan. X gives $20 in free credits after your first credit purchase, plus a match of your first auto-recharge up to $50, though these credits expire after 3 months.
Even if you pay for credits, you cannot call api.x.com/2/... directly from browser JavaScript. On 28 October 2026, the API answered 401 without a token, sent no Access-Control-Allow-Origin header, and its OPTIONS preflight answered 405.
Fetch a public post from the browser
To call the syndication endpoint from the browser, pass the target URL after https://proxy.cors.dev/ (for example https://proxy.cors.dev/https://cdn.syndication.twimg.com/tweet-result?id=20&lang=en&token=a). The free tier requires no signup and no key.
- Generate the token parameter
The
tokenparameter must be present in the query string, but it is not checked. Without it the endpoint returned{}(2 bytes), and any non-empty string returned the full post.react-tweetcomputes it from the post ID as((Number(id) / 1e15) * Math.PI).toString(36)with zeros and dots removed, and the snippet below does the same. - Fetch through the proxy and check the status
Unknown or deleted IDs returned
404with a 3,656-byte HTML error page, not JSON, so check the status before callingres.json(). - Check for withheld posts and decode HTML entities
Return
nullif__typenameis"TweetTombstone". Thetextfield contains HTML entities such as<and&, so decode it before display.
const PROXY = 'https://proxy.cors.dev/';
const decode = (html) => new DOMParser().parseFromString(`<body>${html}`, 'text/html').body.textContent;
const syndicationToken = (id) => ((Number(id) / 1e15) * Math.PI).toString(36).replace(/(0+|\.)/g, '');
async function getPublicPost(id) {
if (!/^\d{1,40}$/.test(id)) throw new Error('Post IDs are numeric strings');
const url = `https://cdn.syndication.twimg.com/tweet-result?id=${id}&lang=en&token=${syndicationToken(id)}`;
const res = await fetch(PROXY + url);
if (res.status === 404) return null;
if (!res.ok) throw new Error(`Syndication answered ${res.status}`);
// Deleted or withheld posts come back as {} or a TweetTombstone
const post = await res.json();
if (post.__typename !== 'Tweet') return null;
return {
text: decode(post.text),
author: `${post.user.name} (@${post.user.screen_name})`,
created: new Date(post.created_at),
likes: post.favorite_count,
replies: post.conversation_count,
photos: (post.photos ?? []).map((photo) => photo.url),
};
}
console.log(await getPublicPost('20'));The free cors.dev tier supports GET and HEAD only, and allows about 600 requests a minute per IP. Past that limit, it answers 429 with a Retry-After header, and there is no daily or monthly quota.
Other free routes: oEmbed and timelines
Two other keyless routes exist: oEmbed and the profile timeline.
| Route | Auth | Browser CORS | Through cors.dev | Returns |
|---|---|---|---|---|
| Syndication tweet-result | none | platform.twitter.com only | 200 on 40 of 40 requests | one post as JSON |
| oEmbed | none | yes | not needed | embed HTML |
| Syndication timeline | none | no | 429 | newest 20 posts inside HTML |
| X API v2 | bearer token | no | Authorization refused on the free tier | full API at pay-per-use prices |
For oEmbed, calling https://publish.twitter.com/oembed?url=<post URL> answers 301 to publish.x.com/oembed. Both send CORS headers, so you can call it directly with no proxy needed (see our guide on oEmbed endpoints and CORS). In our test it returned 630 bytes of JSON with author_name, author_url, and an html blockquote that loads https://platform.x.com/widgets.js. It gives you embed HTML, not structured fields such as like counts.
For profile timelines, https://syndication.twitter.com/srv/timeline-profile/screen-name/<name> answered 429 "Rate limit exceeded" from our server and through cors.dev on 28 October 2026. From residential connections it returned a 143,636-byte HTML page with the newest 20 posts embedded as JSON in a __NEXT_DATA__ script, and no CORS header, so treat it as unusable from the browser. A second timeline URL, https://cdn.syndication.twimg.com/timeline/profile?screen_name=jack, returned 200 with an empty body.
Keep X API tokens on a server
If you need endpoints that only the paid X API provides, do not call api.x.com/2/... from the browser. The free cors.dev tier refuses Authorization headers with 400 header_not_allowed (read more on why the free proxy refuses Authorization).
cors.dev Pro forwards Authorization headers, but a bearer token in browser code is visible to anyone who opens your page. With pay-per-use pricing, anyone holding your token can spend your credits. Always call the X API from your own server.
Good questions.
Does X still have a free API tier for reading posts?
No. Since X launched pay-per-use pricing on 6 February 2026, reading a post costs $0.005, and free credits only come after you buy credits: $20 after the first purchase plus a match of the first auto-recharge up to $50, expiring after 3 months. Public Utility Apps keep free access, and recently active users of the legacy free tier received a one-time $10 voucher.
How is the token parameter for tweet-result calculated?
The token query parameter on https://cdn.syndication.twimg.com/tweet-result must be present, or the endpoint returns {} (2 bytes). However, the value is not checked, and any non-empty string returns the full post. If you want to match react-tweet, calculate ((Number(id) / 1e15) * Math.PI).toString(36) and strip the zeros and dots.
Why does res.json() fail when a post is deleted?
When you request an unknown or deleted post ID from tweet-result, the endpoint returns HTTP 404 with a 3,656-byte HTML error page instead of JSON. You must check res.ok or the status code before calling res.json(). If a post is withheld rather than deleted, the endpoint returns JSON where __typename is "TweetTombstone".
Can I get a tweet's like count from the oEmbed endpoint?
No. publish.x.com/oembed returned 630 bytes of JSON for post 20, containing author_name, author_url and an html blockquote that loads https://platform.x.com/widgets.js. It does not include structured post fields like favorite_count or conversation_count. To get those counts as JSON without the paid API, use the tweet-result syndication endpoint through https://proxy.cors.dev/.
What happens if I read the same post twice on X's pay-per-use plan?
On the paid X API, reading a post costs $0.005, but reading the same resource again within the same 24-hour UTC window is billed once. If you fetch the post through the tweet-result syndication endpoint instead, it does not use the X API or require an X account. Through the free cors.dev tier, you can make about 600 requests a minute per IP with no daily or monthly quota.