Twitter (X) OpenAPI: Which Spec to Use and How to Generate a Typed Client

By TwitterAPI.io Team•14 min read
Diagram of the three Twitter (X) OpenAPI specs. Blue: X official API v2 at api.x.com/2/openapi.json, OpenAPI 3.0.0, version 2.174, 259 operations, 886 schemas, auth Bearer, OAuth 2.0 or OAuth 1.0a. Orange: fa0311/twitter-openapi, a community spec of x.com/i/api, OpenAPI 3.0.3, 93 operations, 86 GraphQL queryId paths, auth with logged-in cookies ct0 and auth_token. Green: TwitterAPI.io at docs.twitterapi.io/api-reference/openapi.json, OpenAPI 3.0.1, 84 operations, auth one X-API-Key header. All three feed a code generator (openapi-generator, openapi-typescript, datamodel-codegen, or X's own XDK) that outputs a typed client, which should be checked against a real response.
Three specs, one generator step. Counts parsed from the spec files; the field check uses 1,204 real posts collected in 2026.
TL;DR
  • Three specs share the name. X official v2 (api.x.com/2/openapi.json, OpenAPI 3.0.0, version 2.174, 259 operations), the community fa0311/twitter-openapi spec of the internal GraphQL API (93 operations, cookie auth), and the TwitterAPI.io spec (docs.twitterapi.io/api-reference/openapi.json, 84 operations, X-API-Key).
  • Auth is the real difference. Of X's 259 operations, 88 accept an app-only Bearer token and 170 need user context (OAuth 2.0 user token or OAuth 1.0a); the community spec needs a logged-in browser session's ct0 and auth_token cookies; ours needs one API key header.
  • Codegen works on all three, with care: npx openapi-typescript (7.13.0) turned our spec into a 6,147-line .d.ts; datamodel-codegen (0.90.0) produced pydantic v2 models in which every Tweet field is optional.
  • Real responses beat the spec: across 1,204 posts all 30 tweet keys were present every time, 22 were always non-null, card was filled on only 28.4% and quoted_tweet on 8.1%; extendedEntities is always an object but carries media on only 31.0%.
  • Check the drift: 6 keys in real responses (extendedEntities, card, article, place, twitterUrl, communityInfo) are not declared in our spec's Tweet schema, and a default pydantic model silently drops them. Set extra='allow' or add them.
  • Cost of the study: 1,204 posts at $0.15 per 1,000 tweets returned came to about $0.18; the same reads at X's pay-per-usage rate card ($0.005 per post read) would be about $6.02.

Search for "twitter openapi" and you land on a mix of things that look alike and are not: a live JSON endpoint on api.x.com, a GitHub repository that reverse-documents the x.com web app, an archived copy on an API directory, and SDK READMEs. They are all OpenAPI files, but they describe different APIs with different hosts, auth, licenses and break risk. Pick the wrong one and your generated client either cannot authenticate or breaks the next time the web app ships.

This page separates the three, shows what each file actually contains (we parsed all three and counted operations, schemas and auth schemes), and walks through generating a typed client in TypeScript and Python. If you want the background on the private web API itself, our explainer on the Twitter GraphQL API covers it; if you want a map of every endpoint by task, see the Twitter (X) API endpoints map. This page stays on the spec files and code generation.

Then the part a spec cannot tell you: we pulled 1,204 recent posts from 10 organization accounts in the API-tooling space (including @XDevelopers and @API) and measured, key by key, what a real tweet object contains: which fields are always there, which are null, which are present but empty, and which the spec forgot. Those numbers decide whether a generated model is safe to use as is.

Tweet keys present on every one of 1,204 real posts

30 of 30, yet only 22 were always non-null, and 6 of the 30 are not declared in the spec's Tweet schema (1,204 posts from 10 organization accounts, collected in 2026).

01 — Section

The three Twitter OpenAPI specs at a glance

Each spec below was fetched and parsed this year; the counts come from the files themselves, not from documentation pages.

SpecDescribesAuth it needsBreak risk
X official v2 (api.x.com/2/openapi.json)The documented X API v2: 259 operations, 886 schemas, version 2.174Bearer token, OAuth 2.0 user token or OAuth 1.0a, per operationVersioned by X; changes are announced on the developer platform
fa0311/twitter-openapi (community)Private endpoints of the x.com web app (x.com/i/api): 93 operations, 86 of them GraphQLCookies of a logged-in account (ct0, auth_token) plus web-client headersHigh: GraphQL query IDs and feature flags follow the web app and change without notice
TwitterAPI.io (docs.twitterapi.io/api-reference/openapi.json)A hosted REST API over public X data: 84 operationsOne X-API-Key headerPaths are stable; response objects carry more keys than the spec declares (measured below)

Which one should you use? If you already have X developer access and want first-party behavior, use X's spec, or skip codegen and install XDK, which X generates from that same file. If you only need to read public data and do not want an X developer account, the TwitterAPI.io spec is the shortest path. The community spec is a reverse-engineering effort of real technical interest, but it automates a logged-in personal session against undocumented endpoints; read its license and X's terms before building on it.

A note on old copies. API directories still mirror an older X file: the APIs.guru listing for twitter.com shows version 2.61, last updated in February 2023, while the live file reports 2.174. Always generate from the live URL, not a mirror.

Two charts. Left: operations per spec by HTTP method. X official v2 has 259 operations (125 GET, 77 POST, 17 PUT, 40 DELETE) and 886 schemas; fa0311 community has 93 operations (76 GET, 17 POST) and 633 schemas; TwitterAPI.io has 84 operations (37 GET, 40 POST, 1 PUT, 3 PATCH, 3 DELETE) and 12 schemas. Right: X official spec auth per operation: 88 accept an app-only Bearer token, 170 need user context, 1 needs no auth.
Operations, schemas and auth parsed from the three spec files, fetched in 2026.
02 — Section

X's official OpenAPI spec: api.x.com/2/openapi.json

X documents the file as an endpoint of its own: GET /2/openapi.json "retrieves the full OpenAPI Specification in JSON format" (docs.x.com, Get OpenAPI Spec). It needs no auth, and it is the only operation in the file with no security requirement. The download is about 1.3 MB of JSON.

What is inside. OpenAPI 3.0.0, info.version 2.174, title "X API v2", one server (https://api.x.com), 201 paths and 259 operations: 125 GET, 77 POST, 40 DELETE and 17 PUT. Every operation has an operationId, which is what gives generated clients readable method names. There are 886 component schemas. The post object is now named Post in the components (36 properties, from id, text and author_id to public_metrics, edit_history_post_ids and note_post), and it declares no required properties, so generated models treat every field as optional.

Auth per operation. The file defines three security schemes: BearerToken (app-only), OAuth2UserToken and UserToken (OAuth 1.0a). Counting each operation's security list: 88 operations accept an app-only Bearer token, 170 require user context, and 1 (the spec itself) needs none. A generated client therefore needs to know which token to send per call.

Generated SDKs. X's own generator, XDK (github.com/xdevplatform/xdk, MIT license), is a Rust tool that "creates language-specific SDKs for the X API from OpenAPI specifications". Its outputs are published as xdk on PyPI (0.10.6) and @xdevplatform/xdk on npm (0.6.6), the versions current when we checked. The older hand-written twitter-api-typescript-sdk repository was last pushed in January 2024.

Cost if you call it. The spec is free; the calls it describes are billed on X's pay-per-usage rate card (docs.x.com/x-api/getting-started/pricing): $0.005 per post read, $0.010 per user read, with pay-per-usage capped at 3M post reads a month and Enterprise above that.

bash
# Download X's live spec and generate a Python client with openapi-generator (needs Java)
curl -s https://api.x.com/2/openapi.json -o x-openapi.json
python3 -c "import json; d=json.load(open('x-openapi.json')); print(d['info']['version'], len(d['paths']), 'paths')"

npx @openapitools/openapi-generator-cli generate \
  -i x-openapi.json -g python -o ./x_client \
  --additional-properties=packageName=x_client

# Or skip codegen: X publishes clients generated from this file
pip install xdk                 # Python
npm install @xdevplatform/xdk   # TypeScript
03 — Section

The community spec: fa0311/twitter-openapi and the internal GraphQL API

The GitHub repository fa0311/twitter-openapi describes itself as the "OpenAPI(Swagger) specification of Twitter Internal API (Twitter graphql API)". It is not an X product. Its spec file (dist/docs/openapi-3.0.yaml, about 850 KB) declares OpenAPI 3.0.3, servers https://x.com/i/api and https://twitter.com/i/api, and 93 operations: 86 under /graphql/{pathQueryId}/... (for example AboutAccountQuery or AudioSpaceById) and 7 older REST paths such as /1.1/friendships/create.json and /2/search/adaptive.json.

Auth. The file declares 23 security schemes, most of them headers the web client sends (SecChUa, ClientTransactionId, UserAgent) alongside the session cookies ct0 and auth_token and a CSRF token. In practice that means every request runs as a logged-in X account. The README also points to a companion relay that runs requests through a logged-in browser.

Activity and packages. The repository is active: it was last pushed in November 2026, when a commit recorded that 39 operations had moved to the web app's current query versions. Generated clients exist for Python (twitter-openapi-python on PyPI, 0.1.6) and TypeScript (twitter-openapi-typescript on npm, 0.1.6), plus a Dart client whose repository has not been pushed since February 2024. A separate fork, thecodrr/twitter-openapi, has not been pushed since June 2023.

License. The spec's info.license is LicenseRef-Custom OR AGPL-3.0-or-later, and the README notes that "the license also inherits to the output", so code you generate from it carries the same terms. That matters for any closed-source product.

Why break risk is high. GraphQL query IDs and feature flags are whatever the x.com frontend sends this week. The project updates them as the web app changes, but your deployed client only follows if you regenerate and redeploy. For the full picture of how that web API works and why it moves, read our explainer on the Twitter GraphQL API; we do not repeat it here.

04 — Section

The TwitterAPI.io OpenAPI spec

Our docs publish the spec at https://docs.twitterapi.io/api-reference/openapi.json, and the docs' machine-readable index at docs.twitterapi.io/llms.txt lists it under "OpenAPI Specs". It is OpenAPI 3.0.1, version 1.0.0, MIT licensed, with one server (https://api.twitterapi.io) and one security scheme: ApiKeyAuth, an X-API-Key header.

What is inside. 84 operations: 75 on the twitter path prefix (search, timelines, users, followers, lists, communities, spaces, trends; for example /twitter/tweet/advanced_search) and 8 on the oapi prefix (filter rules such as /oapi/tweet_filter/add_rule and account monitoring that push to your webhook). By method: 37 GET, 40 POST, 3 PATCH, 3 DELETE and 1 PUT. The Tweet schema declares 24 properties; the user objects are UserInfo (camelCase) and UserProfileSnake (snake_case, the shape follower lists return).

Two things to know before you generate. First, the operations carry no operationId, so tools that name methods from it fall back to names built from the path and method; openapi-typescript is unaffected because it keys types by the literal path ("/twitter/user/last_tweets"). Second, real tweet objects carry 6 keys the Tweet schema does not declare; the next sections measure that and show the fix.

Writes are in the spec too. Many of the POST operations are write actions (/twitter/create_tweet_v2 and others). They run through an authenticated session: one /twitter/user_login_v2 login ($0.10, with a proxy you supply) returns login_cookies, and each post, delete, like, retweet, follow or similar action is then 60 credits ($0.0006). Such a call acts as that account, so X can rate-limit, lock or suspend it; keep a normal human pace, expect the session to need a re-login sometimes, and note that billing is per HTTP 200 call. Reads need no X account at all.

Price, for comparison. Tweet endpoints are pay-per-use at $0.15 per 1,000 tweets returned (15 credits per tweet), with a 15-credit minimum per request, so a search that finds nothing costs $0.00015. X's pay-per-usage rate card (docs.x.com/x-api/getting-started/pricing) lists $0.005 per post read, which is $5 per 1,000, about 33x more. New accounts that pass the automatic bot check get up to $1 of free credits (at most 100,000 credits, no card, one per person or network, expiring after 31 days); see the pricing page for plans.

05 — Section

What a real tweet object contains: 1,204 posts, key by key

A spec says what may appear; a response says what does. We pulled the latest posts of 10 organization accounts through /twitter/user/last_tweets (1,204 posts, 65 requests) and checked every top-level key of every tweet object: is the key there, is the value non-null, is it non-empty, and which JSON types appear.

The shape is stable. All 30 keys were present on all 1,204 objects. No key ever disappeared, so a typed model can declare all 30 instead of guessing.

The values are not. 22 keys were non-null on every post. 6 were nullable: retweeted_tweet (an object on 36.0% of posts), card (28.4%), quoted_tweet (8.1%), article (1.1%), and inReplyToUserId / inReplyToUsername (0.3%). Two were null on every post in this sample: communityInfo and inReplyToId.

KeyNon-empty onType seenModel it as
id, text, createdAt, author, counts100%string / integer / objectrequired
entities91.9%object, sometimes {}required object, optional inner arrays
retweeted_tweet36.0%object or nullnullable Tweet
extendedEntities (media)31.0%always object, {} when no mediarequired object, optional media
card28.4%object or nullnullable object
quoted_tweet8.1%object or nullnullable Tweet
place0.1%always object, almost always {}required object, optional inner fields
source0%always an empty stringstring, do not rely on it

Present but empty is its own case. extendedEntities and place are never null: they are objects that are empty when there is nothing to report. A model that marks them Optional[...] = None passes validation but sends you looking for None when the real signal is a missing media list. source is always an empty string in this sample; treat it as unused rather than as a posting-client signal.

Reply fields do not move together. On 4 posts (0.3%) inReplyToUserId and inReplyToUsername were set while isReply was false and inReplyToId was null: posts that open a new conversation by addressing someone. Model the three fields independently and use isReply when you mean "is this a reply".

Bar chart of 30 tweet keys and the share of 1,204 tweets where each value is non-empty. Eighteen keys such as author, id, text, createdAt and the counts are at 100% (blue). entities 91.9% and extendedEntities 31.0% are present but sometimes empty (grey). Nullable keys in orange: retweeted_tweet 36.0%, card 28.4%, quoted_tweet 8.1%, article 1.1%, inReplyToUserId and inReplyToUsername 0.3%, communityInfo and inReplyToId 0%. place 0.1% and source 0% are always present but empty. Keys marked red with an asterisk are not declared in the TwitterAPI.io spec's Tweet schema: twitterUrl, extendedEntities, card, article, place, communityInfo.
Every key, every time; values vary. 1,204 posts from 10 organization accounts via /twitter/user/last_tweets, collected in 2026.
06 — Section

Spec drift: keys the schema does not declare, and what codegen does with them

Comparing the 30 keys in real responses with the 24 properties of the Tweet schema in our spec, 6 are missing from the schema: extendedEntities, card, article, place, twitterUrl and communityInfo. Nothing in the spec claims to be exhaustive (no additionalProperties: false), so this is legal OpenAPI, but it changes what your generated code does.

Python. We generated pydantic v2 models with datamodel-codegen 0.90.0 and validated a real tweet that carried media. The model accepted it, but pydantic v2 ignores undeclared keys by default, so all 6 undeclared keys, including extendedEntities with the photo or video, were silently dropped. Two fixes work: set model_config = ConfigDict(extra="allow") on the base model, or add the 6 fields to a subclass. The generator also warned that 10 of 44 class names were numbered collisions (Url1 to Url8), so expect a few awkward class names.

TypeScript. openapi-typescript 7.13.0 produced a 6,147-line .d.ts. Types are erased at runtime, so nothing is dropped, but the compiler will not let you read tweet.extendedEntities without a cast. Declare a local extension type once.

X's spec has the same lesson in a different form. Its Post schema declares no required properties, so every generated field is optional, and what you actually receive depends on the tweet.fields and expansions you request. Generated types tell you what can exist, not what will.

The rule. Generate, then validate one real response per endpoint you use, and diff its keys against the model. The runnable script at the end of this page does exactly that for our spec.

python
# pip install datamodel-code-generator pydantic requests
# datamodel-codegen --input openapi.json --input-file-type openapi \
#   --output-model-type pydantic_v2.BaseModel --output models.py
from typing import Any, Optional

from pydantic import ConfigDict

from models import Tweet as GeneratedTweet


class Tweet(GeneratedTweet):
    # keep anything the spec does not declare instead of dropping it
    model_config = ConfigDict(extra="allow")

    twitterUrl: Optional[str] = None
    extendedEntities: dict[str, Any] = {}   # {} when the post has no media
    card: Optional[dict[str, Any]] = None
    article: Optional[dict[str, Any]] = None
    place: dict[str, Any] = {}              # almost always {}
    communityInfo: Optional[dict[str, Any]] = None


def media_urls(t: Tweet) -> list[str]:
    return [m.get("media_url_https", "") for m in t.extendedEntities.get("media", [])]
07 — Section

Generate a typed TypeScript client from the spec

For TypeScript the lightest path is openapi-typescript (types only) plus openapi-fetch (a small typed fetch wrapper). Both read paths and parameters from the spec, so they do not need operationIds. The versions we used were openapi-typescript 7.13.0 and openapi-fetch 0.17.0.

Step 1. Generate the types into your repo and commit them, so a spec change shows up as a diff in review rather than as a runtime surprise.

Step 2. Create the client with the base URL and the X-API-Key header, then call endpoints by their literal path. Parameters and the response body are typed from the spec; the undeclared keys you need are added with an intersection type.

Step 3. Paginate with has_next_page and next_cursor. For /twitter/user/last_tweets the tweets are in data.tweets, 20 per page, newest first; search endpoints such as /twitter/tweet/advanced_search return tweets at the top level.

If you prefer full generated clients with methods (axios or fetch based), openapi-generator with -g typescript-fetch works on all three specs; on ours, expect method names derived from paths.

typescript
// npx openapi-typescript https://docs.twitterapi.io/api-reference/openapi.json -o src/twitterapi.d.ts
// npm install openapi-fetch
import createClient from "openapi-fetch";
import type { paths, components } from "./twitterapi";

type Tweet = components["schemas"]["Tweet"] & {
  twitterUrl?: string;
  extendedEntities?: { media?: { type: string; media_url_https: string }[] };
  card?: Record<string, unknown> | null;
  article?: Record<string, unknown> | null;
};

const client = createClient<paths>({
  baseUrl: "https://api.twitterapi.io",
  headers: { "X-API-Key": process.env.TWITTERAPI_IO_KEY ?? "" },
});

export async function lastTweets(userName: string, maxPages = 3): Promise<Tweet[]> {
  const out: Tweet[] = [];
  let cursor = "";
  for (let i = 0; i < maxPages; i++) {
    const { data, error } = await client.GET("/twitter/user/last_tweets", {
      params: { query: { userName, cursor } },
    });
    if (error || !data) throw new Error(JSON.stringify(error));
    const body = data as any; // tweets live in body.data.tweets
    out.push(...((body.data?.tweets ?? []) as Tweet[]));
    if (!body.has_next_page) break;
    cursor = body.next_cursor;
  }
  return out;
}
08 — Section

Who announces spec changes: @XDevelopers and @API

A spec is only as current as the last time you regenerated, so it helps to know where changes are announced. Two of the 10 accounts in our sample are X's own developer accounts, and their timelines show where the news now lives.

AccountPosts in sampleRepostsDays since newest postOwn posts on API topics
@XDevelopers129 (2.83 a week)82.2%017 of 23 (73.9%)
@API112 (0.29 a week)35.7%35962 of 72 (86.1%)

@XDevelopers is the live channel. 128 of its 129 sampled posts are from 2026. Most of them (82.2%) are reposts, so following it means following the accounts it amplifies, but 17 of its 23 own posts mention API, SDK, endpoint, pricing or similar terms.

@API has gone quiet. Its sample spans 2018 to 2025, with 16 to 22 posts a year from 2018 to 2021, then 13, 7, 8 and 5 a year from 2022 to 2025, and its newest post is from November 2025, 359 days before we collected. Do not rely on it for change notices.

For the community spec, the signal is the repository itself: watch its commits, because a query-version refresh there is exactly the event that breaks a deployed client. For the TwitterAPI.io spec, the changelog and the docs index (docs.twitterapi.io/llms.txt) are the places to watch.

09 — Section

How we collected this

Question. What does a real tweet object contain, key by key, compared with what the specs declare; and how actively do X's developer accounts post about API changes?

Specs. We downloaded api.x.com/2/openapi.json, dist/docs/openapi-3.0.yaml from fa0311/twitter-openapi and docs.twitterapi.io/api-reference/openapi.json in 2026 and counted paths, operations by method, component schemas and security schemes, plus the security requirement of every X operation.

Accounts. The latest posts of 10 organization accounts: @XDevelopers, @API, @OpenAPISpec, @swaggerapi, @getpostman, @github, @vercel, @Docker, @stripedev and @CloudflareDev, through /twitter/user/last_tweets (replies excluded, the endpoint's default), up to 7 pages each. Sample: 1,204 posts, 65 requests, collected in 2026. The window per account runs from its oldest to newest sampled post and varies widely, because some accounts post daily and others rarely.

Measures. For each top-level key of each tweet and author object: present, non-null, non-empty, JSON types seen. Codegen was checked with openapi-typescript 7.13.0 and datamodel-code-generator 0.90.0 on our spec.

What the sample cannot show. Replies were excluded, so reply-specific fields are underrepresented; organization accounts post more media and cards than typical users; other endpoints (search, mentions) return the same tweet object, but we did not measure them here. The @API and @XDevelopers topic shares use a simple keyword match on post text.

Cost. 1,204 posts at 15 credits each is about $0.18 at TwitterAPI.io prices. The same 1,204 post reads at X's pay-per-usage rate ($0.005 each) would be about $6.02.

Privacy. Aggregates only, organization accounts only, no analysis of reposted or quoted authors. Field presence counts keys, not content.

python
"""Download the TwitterAPI.io OpenAPI spec, call one endpoint, and diff a real
tweet object against the spec's Tweet schema.

pip install requests
export TWITTERAPI_IO_KEY=...
"""
import os

import requests

BASE = "https://api.twitterapi.io"
SPEC_URL = "https://docs.twitterapi.io/api-reference/openapi.json"
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}

# 1. Read the spec: operations and the declared Tweet properties
spec = requests.get(SPEC_URL, timeout=30).json()
ops = [(m.upper(), p) for p, item in spec["paths"].items()
       for m in item if m in ("get", "post", "put", "patch", "delete")]
print(f"{spec['info']['title']} {spec['info']['version']}: {len(ops)} operations")
declared = set(spec["components"]["schemas"]["Tweet"]["properties"])

# 2. Fetch real tweets (20 per page, newest first), following the cursor
tweets, cursor = [], ""
for _ in range(3):
    r = requests.get(
        f"{BASE}/twitter/user/last_tweets",
        headers=HEADERS,
        params={"userName": "XDevelopers", "cursor": cursor},
        timeout=30,
    )
    r.raise_for_status()
    body = r.json()
    if body.get("status") != "success":
        raise SystemExit(body.get("msg"))
    tweets += (body.get("data") or {}).get("tweets") or []
    if not body.get("has_next_page"):
        break
    cursor = body.get("next_cursor", "")

# 3. Diff the real keys against the schema and report null / empty values
seen = set().union(*(t.keys() for t in tweets)) if tweets else set()
print(f"{len(tweets)} tweets, {len(seen)} keys")
print("in responses, not in the schema:", sorted(seen - declared))
print("in the schema, never seen:", sorted(declared - seen))
for key in sorted(seen):
    null = sum(1 for t in tweets if t.get(key) is None)
    empty = sum(1 for t in tweets if t.get(key) in ("", [], {}))
    if null or empty:
        print(f"  {key:20} null {null:3}  empty {empty:3}  of {len(tweets)}")

# 4. Media lives in extendedEntities (an empty object when there is none)
for t in tweets[:5]:
    media = (t.get("extendedEntities") or {}).get("media") or []
    print(t["createdAt"], t["likeCount"], [m.get("type") for m in media])

Researching this?Free starter credits cover end-to-end testing — Google sign-in, no card. Get a key.

10 — Questions

Questions readers ask

Does X (Twitter) publish an official OpenAPI spec?

Yes. X serves its API v2 spec at https://api.x.com/2/openapi.json and documents it as the Get OpenAPI Spec endpoint on docs.x.com. It needs no auth. When we parsed it in 2026 it was OpenAPI 3.0.0, version 2.174, with 259 operations and 886 schemas.

What is fa0311/twitter-openapi?

A community project on GitHub that writes an OpenAPI spec for the private GraphQL endpoints the x.com web app uses (93 operations, 86 of them GraphQL). It requires a logged-in session's cookies, changes whenever the web app's query versions change, and is dual licensed under a custom license or AGPL-3.0, a license its README says carries over to generated code. It is not an X product.

How do I generate a Python client from the Twitter OpenAPI spec?

For models, run datamodel-codegen --input openapi.json --input-file-type openapi --output-model-type pydantic_v2.BaseModel --output models.py; for a full client, run openapi-generator with -g python. For X's own API you can also install xdk, which X generates from its spec. With pydantic, set extra="allow" or declare missing fields, because undeclared keys are dropped by default.

How do I generate TypeScript types from the Twitter OpenAPI spec?

Run npx openapi-typescript <spec-url> -o twitterapi.d.ts and use openapi-fetch for a typed fetch client keyed by path. On the TwitterAPI.io spec this produced a 6,147-line type file with openapi-typescript 7.13.0. For X's API, the @xdevplatform/xdk npm package is generated from X's spec.

Is there an OpenAPI spec for TwitterAPI.io?

Yes, at https://docs.twitterapi.io/api-reference/openapi.json, listed in the docs index docs.twitterapi.io/llms.txt. It covers 84 operations with a single X-API-Key header. Real tweet objects carry 6 keys the Tweet schema does not declare (extendedEntities, card, article, place, twitterUrl, communityInfo), so extend generated models with them.

Why is the APIs.guru copy of the Twitter spec different?

It is an old snapshot. The APIs.guru listing for twitter.com shows version 2.61, last updated in February 2023, while X's live file reported version 2.174 when we checked. Generate from the live URL.

Which Twitter OpenAPI fields can be null?

In our sample of 1,204 posts, retweeted_tweet, card, quoted_tweet, article, inReplyToUserId, inReplyToUsername, communityInfo and inReplyToId were null on some or all posts. extendedEntities and place are never null but are often empty objects, and source was always an empty string.

11 — Further reading

Continue

Sources & further reading
More from this series

Writing about something you built with TwitterAPI.io? Our community rewards program pays API credits for honest tutorials, build logs and videos.

Build it

Stop reading. Start building.

Starter credits cover real testing on real data. Google sign-in, no card, no application queue.

Get an API key

Already building with TwitterAPI.io? Publish an honest tutorial or build log about it and earn up to $200 a month in API credits.