People still love Twitter threads — long, opinionated essays written one tweet at a time. And people still hate reading them in Twitter's own UI. So the idea was simple: a tool that takes a thread URL and turns it into a clean article. Paste the link, get the unrolled thread as one continuous piece of writing.
I thought it would take a weekend. It didn't — and not because the idea is hard. The platform made sure of that.
The Simple Idea That Wasn't
Here's the whole mental model I started with:
- User pastes a Twitter thread URL
- I extract the tweet ID from it
- I fetch the conversation data
- I stitch the tweets into a markdown article
- Done, ship it
Step 3 is where the dream died.
What Twitter Did to Its API
Twitter used to have one of the friendliest developer ecosystems on the internet. Free API access, generous tiers, third-party apps everywhere. Then the acquisition happened, and within about four months the whole landscape flipped:
- The price shot up. What cost nothing became three tiers: a free plan capped at 1,500 posts a month (write-only, almost useless for reading data), a $100/month "Basic" tier, and enterprise access reported at $42,000 a month. For a solo developer running a portfolio tool, those numbers are a "go away."
- Every third-party client got killed. In January 2023, apps like Tweetbot and Twitterrific — beloved, years-old apps — were cut off without warning, and the developer terms were quietly updated to ban "a substitute or similar service or product" to Twitter's own apps.
- Even the researchers got shut out. The Verge reported the new costs "closed the book" on academic research — one estimate put the damage at over 250 jeopardized projects.
If you're a big company with a marketing budget, fine, pay the toll. If you're an indie dev building a utility, the official API is a non-starter. Effectively, Twitter had banned the kind of app I was building.
The Underground: TwtAPI
When a platform closes its official doors, an unofficial market opens. Twitter's own website still needs to fetch threads — it talks to internal GraphQL endpoints that nobody documents but that work anyway. Enter TwtAPI (www.twtapi.com), one of the services that reverse-engineers those endpoints and resells access per-call. Send your key as an X-API-Key header, get the raw GraphQL payload the official client gets.
I'll be honest: I don't know exactly who runs it. The docs suggest a Chinese operation, but honestly it doesn't matter. It works, it stays current with Twitter's churn, and for a hobby tool that's exactly what I needed.
The Parsing Nightmare
The nasty discovery was that "the API" isn't one thing. TwtAPI routes to Twitter's internal GraphQL, and that GraphQL is a shapeshifter — the same endpoint returns two completely different response shapes depending on... I genuinely don't know. Route? Data availability? The phase of the moon? My parser handles both:
// SCENARIO A: Direct Tweet Format (TweetDetailv2 shape)
if (data?.tweetResult?.result) {
rootTweetNode = data.tweetResult.result;
}
// SCENARIO B: Timeline Format (TweetDetailConversationv2 shape)
else if (data?.timeline_response?.instructions) {
const addEntries = instructions.find((i) =>
i.__typename === 'TimelineAddEntries' || i.type === 'TimelineAddEntries'
);
const entries = addEntries?.entries || [];
const rootEntry = entries.find((e) => e.entryId?.startsWith('tweet-'));
// Even the nesting is inconsistent
const content = rootEntry?.content?.content || rootEntry?.content?.itemContent;
const tweetResult = content?.tweetResult?.result || content?.tweet_results?.result;
rootTweetNode = tweetResult ?? null;
}
Two spellings for the same field (tweetResult.result vs tweet_results.result), two containers (content vs itemContent), two ways to identify an instruction (__typename vs type). Every fallback is a scar from an actual payload that broke a previous version.
And some tweets come back wrapped in something called TweetWithVisibilityResults — a container the tweet hides inside and must be peeled off before you get the real data. Peeling a tweet out of a result that wraps a tweet. Because why not.
I also added a failsafe that dumps the full raw payload to the console when the root tweet can't be found. When Twitter silently renames a field, a parser doesn't crash — it just quietly returns garbage. The dump is the only way to see the new shape. That dump is literally how I wrote this file.
The deepest irony: there's no algorithm here. Just defensive navigation of a data structure that refuses to commit to a shape. That's not engineering, that's archaeology.
Three Hundred Calls a Month
The catch with a proxy service is the quota: TwtAPI's free tier gives you 300 API calls a month. That's the entire budget for the tool — and it's public. An endpoint that hands out API calls is a gift anyone can spend on your behalf.
So the parser got three layers of protection:
- Caching. Next.js's built-in fetch cache with a 24-hour revalidation window. Fetch a thread once, and every visitor for the next day gets it from the cache instead of TwtAPI.
- Rate-limit backoff. A simple exponential backoff on 429s — 1s, 2s, 4s, give up — so a transient rate limit doesn't burn calls on retry spam:
if (response.status === 429 && attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
- A spending budget. Anyone who found my endpoint could drain the month's quota without ever paying for a key of their own. So I added a per-process sliding-window budget — a cap on live API calls per minute, rejecting anything beyond it. For a 300-calls-a-month free tier, that guard isn't optional. It's the difference between a working tool and a quota gone in an afternoon.
Closing Thoughts
Building a thread unroller should have been a two-file utility. Instead it was a crash course in what it means to build on a platform that decided it doesn't want third-party developers anymore.
The churn is the story. Twitter's internal API changes so often that proxies live in a perpetual chase to keep up, and consumers like me sit at the end of that chain — holding a parser whose every field access is a shrug. The code I wrote today might not work next month.
I don't have a tidy ending where I reverse-engineered Twitter once and it worked forever. Nobody does. What I have is a working tool, a parser full of hard-won fallbacks, and a much deeper respect for the proxy services quietly keeping the third-party ecosystem alive. They're doing the work Twitter doesn't want done — and for indie devs, they're the only reason this kind of project still exists.
Sources:
- The Verge — Twitter announces new API pricing, posing a challenge for small developers
- The Verge — Twitter just closed the book on academic research
- The Verge — Twitter to remove free API access
- TechCrunch — Twitterrific, Tweetbot hit by twitter API restrictions
- The Iconfactory — Twitterrific: End of an Era
- Tapbots — Tweetbot memorial
- MacRumors — Twitter Officially Bans All Third-Party Apps
- TwtAPI — Twitter Data API
- TwtAPI — Documentation