X-Autopilot

X API TypeScript: the official XDK and what is dead in it

X's TypeScript XDK is first-party, MIT-ish and frozen since July 2026. Three of its config fields and two of its client methods do nothing. Read before you ship.

X-Autopilot Team··9 min read
On this page · 8 sections

The short version

  • ▸The package is @xdevplatform/xdk. Latest is 0.6.6, published 25 July 2026, and nothing has shipped since: the repo's last push is the same day and its only issue filed after that date has sat for seven months with no reply. Treat it as stable-by-abandonment rather than maintained.
  • ▸ClientConfig accepts retry and maxRetries, and the shipped type calls them 'Whether to automatically retry failed requests'. The constructor stores both and the request path never reads either. There is no retry loop and no 429 backoff anywhere in the bundle.
  • ▸Client.isTokenExpired() returns false unconditionally and Client.refreshToken() has an empty body. Real refresh logic exists, but on the separate OAuth2 class, and ClientConfig has no field to hand an OAuth2 instance to the client, so the documented wiring 401s once the access token expires.
  • ▸Responses are recursively camelCased before you see them: created_at arrives as createdAt, public_metrics as publicMetrics. Request bodies are converted back to snake_case on the way out. None of the five core doc pages mention either direction, and the examples only touch .data.username, which reads the same both ways.
  • ▸The bundle ships articles, broadcasts and chat sub-clients that the published TypeScript reference index does not list, and no bots client at all. The reference itself was generated from v0.4.0 against X API v2.152, while POST /2/tweets now reports 2.172.

Quick answer

The X API TypeScript SDK is @xdevplatform/xdk, generated from X's own OpenAPI spec. It is worth installing for the typed surface and the OAuth 1.0a signing, and it is not worth trusting as a resilience layer: retry and maxRetries are accepted and then never read, Client.isTokenExpired() returns false unconditionally, and Client.refreshToken() is an empty function. Latest is 0.6.6 from 25 July 2026 and nothing has shipped since.

Last updated: October 2026

Everything below comes from unpacking the published tarball and reading the generated code, not from the docs. The Python sibling got the same treatment in X API Python, and the two packages fail in different places, which is useful if you are choosing a language for a bot.

What you actually install

npm install @xdevplatform/xdk resolves to 0.6.6, published 25 July 2026. Fifteen versions exist, the first on 3 November 2025, the same day X announced both XDKs on its developer forum. Six of the fifteen went out inside four hours on 25 July, which reads like a release-pipeline fight, and then the repo stopped: xdevplatform/xdk-typescript shows its last push on that date.

Two installation traps before you write a line:

First, the next dist-tag still points at 0.2.5-beta from 29 November 2025. If your habit is to pull prereleases, @xdevplatform/xdk@next hands you something eight months older than latest, not newer.

Second, node-fetch is declared as a peer dependency at ^3.3.0, and the install page does not mention it. The HTTP client checks for a global fetch and, when it is missing, requires node-fetch or throws X API SDK: node-fetch not found. The docs say Node.js 16 or later; global fetch only arrived unflagged in Node 18. So the stated floor works only if you also install the peer.

Licensing is messier than it looks. package.json says MIT, and the generator repo xdevplatform/xdk carries a real MIT licence. The output repo xdk-typescript has no licence file, and the published tarball ships only dist, README.md and package.json. The Python output repo has the same gap. If your legal review needs a licence in the artifact, that is a conversation.

The config fields the client ignores

ClientConfig has nine fields. Three of them are a trap:

FieldShipped descriptionWhat the code does
retry"Whether to automatically retry failed requests"assigned, defaults to true, never read
maxRetries"Maximum number of retry attempts"assigned, defaults to 3, never read
oauth1typed anypassed through, works

this.retry and this.maxRetries appear exactly twice in the whole bundle, both in the constructor. Client.request() builds headers, awaits one httpClient.request, and throws an ApiError if the response is not ok. There is no loop, no delay, no inspection of x-rate-limit-reset.

That matters because of what the launch post promised on 3 November 2025:

You can simply specify the maximum number of Posts or User objects you'd like returned and the XDK will take care of the pagination and rate limits.

Pagination, yes. Rate limits, no. And oauth1?: any in a package whose first listed feature is "full type safety" is the kind of detail you only find by opening index.d.ts.

Token refresh: the method that does nothing

This is the one that bites in production, because autocomplete leads you straight into it.

Client exposes isTokenExpired() and refreshToken(), both with doc comments. isTokenExpired() is return false;. refreshToken() is an empty async body. Neither is marked deprecated or internal.

Real refresh logic does exist, on the separate OAuth2 class, and it is fine: refreshToken() posts grant_type=refresh_token to https://api.x.com/2/oauth2/token, uses Basic auth when a client secret is present and client_id in the body when it is not, and isTokenExpired() applies a 60-second buffer against a stored expiry. That was issue 1 on the repo, filed 17 December 2025, and it got built.

What never got built is the wiring. ClientConfig accepts bearerToken, accessToken or oauth1. There is no oauth2 field. X's own authentication guide ends with:

const tokens = await oauth2.exchangeCode(authCode, codeVerifier);
const client = new Client({ accessToken: tokens.access_token });

That client now holds a dead string. X API OAuth 2.0 user access tokens are short-lived, so a long-running process built exactly as documented starts returning 401 after its first expiry window, and the client method you would reach for to detect that always says the token is fine. Keep the OAuth2 instance alive, check oauth2.isTokenExpired() yourself on a timer, and reassign client.accessToken after each refresh. If the auth model itself is what you are deciding, X API OAuth covers which flow each endpoint actually needs.

Responses come back camelCase now

Every response passes through a recursive transformKeys before it reaches you, so created_at becomes createdAt, public_metrics becomes publicMetrics, in_reply_to_user_id becomes inReplyToUserId. Request bodies go the other way through transformKeysToSnake on the way out. Query parameters are normalised from either style.

This is the fix for issues 2 and 4, filed 30 December 2025 and 2 January 2026, when the types were camelCase but the payload was raw snake_case. Both are closed and the fix is real. The problem is that it landed in the code and never in the prose: none of the five core TypeScript doc pages mention key transformation in either direction, and every example touches .data.username or .data.id, which are identical under both conventions. So the behaviour is invisible until you ask for a field with an underscore in it.

Net effect for anyone pasting from the X API reference, Stack Overflow or an AI answer: the field names in the docs are not the field names you get. That cuts both ways, since material written against 0.3.0 is now wrong in the opposite direction.

One more wire-level oddity. Posts endpoints serialise their field list as post.fields, while streaming endpoints serialise theirs as tweet.fields. Same SDK, same release, two parameter spellings.

Three sub-clients the reference does not list

Count them in the bundle: 21 sub-clients are exported and instantiated by the Client constructor. The published TypeScript reference index lists 18 of them. Missing: ArticlesClient, BroadcastsClient, ChatClient. They work; client.chat.getUserPublicKeys() shows up in a real bug report. They are simply absent from the generated reference, exactly as chat, articles and broadcasts were absent from the Python reference.

Nothing exposes bots, so the passwordless bot-account endpoints are a raw-HTTP job in both languages.

That reference also carries its own age in the header: it was generated from v0.4.0, which npm dates to 21 January 2026, against X API v2.152. The live POST /2/tweets OpenAPI block currently reports 2.172. Two minor SDK versions and twenty API revisions of drift between the docs you read and the package you installed.

Watch out for a related trap: followUser is still typed and shipped, but following, likes and quote posts were removed from self-serve tiers on 20 April 2026. A generated SDK keeps the method long after the plan loses the right to call it. The same applies to replies, which now only succeed under one narrow condition described in X API reply.

Undocumented exports are more useful than the missing ones: CryptoUtils, hmacSha1, generateNonce, generateTimestamp and the shared httpClient instance are all public. The six subpath exports (/client, /oauth2, /oauth1, /crypto, /http-client) all resolve to the same bundle, so importing from them buys you nothing. And dist ships a 62 KB .tsbuildinfo, which is a build artifact nobody needs.

Pagination and streaming, measured against the promise

Pagination is honest work. Paginator holds a fetchPage closure, tracks nextToken, implements both Symbol.iterator over fetched items and Symbol.asyncIterator that fetches pages as you walk, and the PostPaginator, UserPaginator and EventPaginator subclasses add typed posts, users and events getters. Async iteration genuinely works, and it is the one place the docs and the code agree.

Two caveats. allItems accumulates every item fetched for the lifetime of the paginator and items returns a fresh copy on every access, so a full followers walk holds the entire list in memory. And there is no limit argument anywhere: fetchLast(count) is the closest thing, and the async iterator runs to exhaustion. On a 3 million Post read cap and $0.005 per Post read, an unbounded walk is a billing event, not just a memory one. X API pricing has the full rate card.

Streaming is the part the TypeScript XDK clearly does better than its Python sibling. The chunk handler calls decoder.decode(value, { stream: true }) and buffers until a newline before parsing, which is the correct way to handle a multi-byte character landing on a TCP boundary. The Python XDK decodes per chunk and crashes on exactly that, and the bug has been open since May 2026. Invalid JSON lines here are warned and skipped rather than fatal, keep-alives get their own event, and auto-reconnect is togglable.

Where it stands

Five of the seven filed issues got real fixes between December 2025 and July 2026, which is a better record than the Python repo's four open and unanswered. Then the lights went out. Issue 7, opened 3 March 2026, asks how to recover a private key from the Juicebox key store to send an XChat message and notes that the send-message example is missing. Seven months, zero comments. If you are building on encrypted DMs, X Chat API covers what the endpoints do and do not deliver.

Use the XDK for the types, the OAuth 1.0a signing and the stream parser. Supply your own retry policy, your own token lifecycle and your own pagination bounds, and do not assume a field name from the X API docs survives the trip. A tweepy-style community library does not exist for TypeScript at the same maturity, so the practical alternative is a thin fetch wrapper that imports the XDK's types and skips its client.

And if what you actually want is an X account that posts and replies on its own rather than an SDK to build one with, that is a different build. X-Autopilot drives your own logged-in browser session on your Mac instead of going through the API, which avoids the per-request rate card and the summon rule entirely, at the cost of being outside the sanctioned API route rather than inside it. That tradeoff is the real decision; the SDK's dead config fields are just the cost of the other path.

Frequently asked

Answers indexed by Google + AI assistants.

What is the official X API SDK for TypeScript?+

The TypeScript XDK, published as @xdevplatform/xdk on npm and generated from X's own OpenAPI spec by the xdevplatform/xdk generator. X announced it alongside the Python XDK on 3 November 2025. Latest is 0.6.6 from 25 July 2026, with 15 versions in total and no runtime dependencies.

Does the X TypeScript XDK retry rate-limited requests?+

No. ClientConfig exposes retry (defaulting to true) and maxRetries (defaulting to 3), and the constructor assigns both to the client, but those two properties appear nowhere else in the shipped bundle. Client.request() makes one fetch call and throws an ApiError on a non-ok response. The Paginator marks itself rateLimited on a 429 and then rethrows, so a for-await walk aborts mid-pagination. Write your own backoff.

How do I refresh an OAuth 2.0 token with the X TypeScript XDK?+

Call refreshToken() on the OAuth2 class, not on the Client. OAuth2.refreshToken() posts grant_type=refresh_token to https://api.x.com/2/oauth2/token and handles both confidential and public clients, and OAuth2.isTokenExpired() takes a 60-second buffer. The Client's same-named methods are stubs: isTokenExpired() returns false and refreshToken() does nothing. Since ClientConfig only accepts a bearerToken string, an accessToken string or an oauth1 instance, you have to refresh yourself and build a new client or overwrite client.accessToken.

Why is response.data.publicMetrics undefined in my X API code?+

Check which direction you have the casing. In 0.6.6 the client runs every response through a recursive snake-to-camel transform, so the X API's public_metrics arrives as publicMetrics and created_at as createdAt. Code written against 0.3.0, where the types were camelCase but the payload was still snake_case, now breaks the other way. Issues 2 and 4 on the repo are that original complaint, both closed.

Does the X TypeScript XDK work on Node 16, Cloudflare Workers and React Native?+

The install page says Node.js 16 or later and package.json sets engines to >=16.14, but global fetch only arrived unflagged in Node 18. On older runtimes the HTTP client falls back to requiring node-fetch, which is declared as a peer dependency at ^3.3.0 and is not mentioned on the install page; without it the client throws 'X API SDK: node-fetch not found'. The Cloudflare Workers 'Illegal invocation' bug from issue 5 is fixed: fetch is now bound to globalThis in every environment branch. React Native has its own branch too.

Should I use the XDK or write raw HTTP calls against the X API?+

The XDK earns its place for the typed surface: a 599 KB index.d.ts with enumerated field names per endpoint is genuinely hard to hand-write, and the OAuth 1.0a signing and newline-buffered stream parser are real work you would otherwise repeat. It does not earn its place as a resilience layer. A thin wrapper of your own around fetch plus the XDK's types is a defensible middle path.

Sources
Related searches
x api typescriptx api javascript sdk@xdevplatform/xdkx api node jstwitter api typescript clientx api sdk refresh tokenxdk typescript paginationx api stream typescriptx api pythonx api oauthx api pricingxdkx api v2 sdknode-fetchx activity api
DY
Deepak YadavBuilding X-Autopilot

Product designer and indie hacker. Runs the agent on his own X account every day and writes up what the data shows, including when it's inconvenient.

Follow on X →
Try X-Autopilot.
$199 once. No subscription, no monthly bill. Real Chrome on your Mac.
See pricing▶
Free PDF · the X Growth Playbook

The exact playbook we use to grow on X

The bio that converts, the daily reply loop, the posting cadence, and the tool stack, in one no-fluff PDF. Drop your email and it's yours.

No spam. Unsubscribe anytime.

Free tools

Try it yourself.

All free X tools →
Keep reading

Related posts.