Docs
Simple format

Simple format

Opt-in compact JSON for every read endpoint. Add format=simple and get flat, camelCase tweets, users, lists, communities and jobs with a fixed schema and a cursor that is null on the last page.

By default every endpoint returns X's raw JSON (see Response format). That does not change. If you would rather work with small, flat objects, add format=simple to the query string, or send the header X-Response-Format: simple:

curl 'https://api.twittrapi.com/user/2244994945/tweets?count=20&format=simple' \
  --header 'X-API-KEY: YOUR_API_KEY'
{
  "data": [
    {
      "id": "2019881223666233717",
      "url": "https://x.com/XDevelopers/status/2019881223666233717",
      "text": "Officially launching X API Pay-Per-Use … We’re so back. http://developer.x.com",
      "createdAt": "2026-02-06T21:09:45Z",
      "lang": "en",
      "author": { "id": "2244994945", "username": "XDevelopers", "name": "Developers", "...": "..." },
      "counts": { "likes": 5599, "retweets": 896, "replies": 791, "quotes": 596, "bookmarks": 2803, "views": 2552260 },
      "media": [],
      "urls": ["http://developer.x.com"],
      "hashtags": [],
      "mentions": [],
      "replyTo": null,
      "quoted": null,
      "retweetOf": null,
      "conversationId": "2019881223666233717",
      "isPinned": true,
      "source": null,
      "article": null
    }
  ],
  "nextCursor": "DAAHCgABHUS5pXT__-wLAAIAAAATMjEwMjQ0NzU2NzA0MDgyMzc2MAgAAwAAAAIAAA",
  "previousCursor": "DAAHCgABHUS5pXUAJxELAAIAAAATMjEwODk2ODM2MDY5MjQwNDI2MQgAAwAAAAEAAA",
  "hasNextPage": true
}
  • Same price, same parameters, same errors. Only the body of a successful (200) response changes.
  • The response carries the header X-Response-Format: simple, so you can tell which format you got.
  • Every reference page has a Raw / Simple toggle on its example response.
  • Account actions (/client/*) support it too: instead of the {"status_code", "response"} wrapper you get the new tweet, the user, the media ID, or {"data": {"success": true}} for actions such as like or bookmark. When X reports an error inside the wrapper, the wrapper is returned unchanged. Direct messages (/client/dm/*) return conversations, messages and settings in this format. /premium/* ignores the option.

Envelope

Lists and timelines:

{ "data": [ ... ], "nextCursor": "…" | null, "previousCursor": "…" | null, "hasNextPage": true | false }

Single objects:

{ "data": { ... } }

data is null when X returns nothing for the ID or handle (for example GET /user/by/username/{username} for a handle that does not exist).

Pagination

Pass nextCursor as cursor to get the next page. nextCursor is null, and hasNextPage is false, when there is nothing more to fetch:

  • the page has no items,
  • X returns the same cursor you sent,
  • X's end marker (follower and following lists answer 0|… on the last page).

So this loop always ends:

import requests
 
cursor, tweets = None, []
while True:
    params = {"count": 20, "format": "simple", **({"cursor": cursor} if cursor else {})}
    page = requests.get("https://api.twittrapi.com/user/12/tweets", params=params,
                        headers={"X-API-KEY": "YOUR_API_KEY"}).json()
    tweets += page["data"]
    if not page["hasNextPage"]:
        break
    cursor = page["nextCursor"]

previousCursor is X's top cursor: use it to poll for newer items.

Rules

  • Field names are camelCase and always present. A value X did not send is null; a list X did not send is []. Counts X does not send are 0.
  • IDs are strings. Times are ISO 8601 in UTC (2026-02-06T21:09:45Z).
  • Promoted tweets are dropped. Conversation threads (modules) are flattened in display order. A tweet that appears twice on a page is listed once.
  • Tweets X does not return (deleted, suspended author, withheld) appear as {"id": "…", "unavailable": true} when their ID is known, and are left out otherwise. The same placeholder is used for quoted.
  • Error responses (any status other than 200) are unchanged, see Errors. A 200 response that only holds X's errors (no data) is also returned raw, so you see X's message.
  • New fields may be added. Existing fields are not renamed or removed.

Tweet

FieldTypeNotes
idstring
urlstringhttps://x.com/<username>/status/<id>
textstringFull text: the long version of long posts (note tweets), t.co links replaced by their targets, the trailing media link removed, &amp; &lt; &gt; decoded. Retweets: RT @user: plus the full original text.
createdAtstring | nullISO 8601
langstring | nullX's language code (en, und, …)
authorUser | null
countsobjectlikes, retweets, replies, quotes, bookmarks, views (views is null when X hides it)
mediaMedia[]
urlsstring[]Expanded URLs in the text
hashtagsstring[]Without #
mentions{id, username}[]
replyToobject | null{tweetId, userId, username} when the tweet is a reply
quotedTweet | {id, unavailable} | nullOne level: the quoted tweet's own quoted is null
retweetOfTweet | nullThe original tweet of a retweet (with its quoted)
conversationIdstring | nullID of the first tweet of the thread
isPinnedbooleantrue for the pinned tweet at the top of a profile timeline
sourcestring | nullApp name, when X sends it (Twitter Web App)
articleobject | nullX Article: {id, title, previewText, coverImageUrl, publishedAt, text}

Media

FieldTypeNotes
typestringphoto, video or gif
urlstringPhotos: the image. Videos and GIFs: the MP4 with the highest bitrate.
previewUrlstringThe image (poster frame for videos)
width, heightnumberOriginal size
altTextstring | null
durationMsnumber | nullVideos only

User

FieldTypeNotes
idstring
usernamestringHandle without @
namestringDisplay name
descriptionstringBio, t.co links expanded
locationstringAs written by the user
urlstring | nullWebsite, expanded
createdAtstring | nullISO 8601
avatarUrlstring | nullFull size (not the 48 px _normal version)
bannerUrlstring | null
verifiedbooleanBlue check
verifiedTypestring | nulle.g. Business or Government; null for none
protectedbooleanPrivate account
countsobjectfollowers, following, tweets, likes, media, listed
pinnedTweetIdsstring[]
isAutomatedbooleanAccount carries X's "Automated" label

GET /user/about/{username} adds about: accountBasedIn, locationAccurate, createdCountryAccurate, source, usernameChanges, lastUsernameChangeAt, affiliateUsername, identityVerified, verifiedSince.

List

FieldTypeNotes
id, name, descriptionstring
createdAtstring | nullISO 8601
memberCount, followerCountnumber
privateboolean
bannerUrlstring | null
owner{id, username} | null

Community

FieldTypeNotes
id, name, descriptionstringdescription is empty in search results
createdAtstring | nullISO 8601
memberCount, moderatorCountnumber
joinPolicystring | nullAs X sends it, e.g. Open
nsfwboolean
topicstring | null
bannerUrlstring | null
creator{id, username} | null
rules{id, name, description}[]

Job

FieldTypeNotes
id, titlestring
location, url, descriptionstring | nullurl is the employer's application page
company{id, name, logoUrl} | null
postedBy{id, username} | null

GET /jobs/find-location/{term} returns {id, name} items to use as the job search location.

GET /trends/{locationId}: items {name, query, url, tweetVolume}; query is URL-decoded (#GoLang), tweetVolume is null when X has no count. GET /misc/locations: items {woeid, name, country, countryCode, placeType, parentId}.

Endpoints

Endpointdata
/search (and its aliases), /tweet/{tweetId}, /tweet/{tweetId}/article, /tweet/{tweetId}/quotes, /user/{userId}/tweets, /tweetsAndReplies, /media, /likes, /articles, /communities/{communityId}/tweets, /lists/tweetsTweet[] (search with category=People: User[])
/simpletweet/{tweetId}Tweet
/tweet/{tweetId}/favorites, /retweets, /user/{userId}/followers, /followings, /verifiedFollowers, /user/users/by/ids, /communities/{communityId}/members, /members/search, /moderators, /lists/members, /lists/followersUser[]
/user/by/username/{username}, /user/{userId}User
/user/about/{username}User with about
/user/friendships/show{source, target, following, followedBy, canDm, blocking, blockedBy, muting}
/communities/search/{searchTerm}Community[]
/communities/{communityId}Community
/lists/detailsList
/jobs/search/{searchTerm}Job[]
/jobs/{jobId}Job
/jobs/find-location/{term}{id, name}[]
/trends/{locationId}Trend[]
/misc/locationsTrendLocation[]
/username/to/id/{username}, /user/usernames/to/ids, /user/usernames/status, /misc/emojisThe usual response, unchanged, inside {"data": ...}. These are already small (or, for hashflags, a flat list), so there is nothing to simplify.
POST /client/create, /client/replyTweet (a scheduled /client/create: {success: true, scheduledTweetId})
POST /client/home-timeline, /client/bookmarksTweet[]
POST /client/schedule-overview{id, text, executeAt, state, mediaIds}[]
POST /client/follow, /client/profile, /client/profile/image, /client/profile/bannerUser
POST /client/media, /client/media/video{mediaId, mediaKey, expiresAfterSecs}
POST /client/communities/createCommunity
POST /client/retweet{success: true, retweetId}
POST /client/like, /client/deleteTweet, /client/bookmark, /client/unbookmark, /client/schedule-delete, /client/report, /client/communities/remove{success: true}
POST /client/dm/*Conversations, messages and DM settings; the fields are on each endpoint page.

Size and speed

Across the 9 captured endpoints the simple format is 64 % smaller in bytes (median, range 58–70 %) and uses 61 % fewer input tokens (median, range 52–68 %). Tokens shrink a little less than bytes because the fields that are dropped are mostly long, repetitive keys and IDs, which tokenize efficiently.

EndpointRaw bytesSimple bytesBytes savedRaw tokensSimple tokensTokens saved
/user/{userId}/tweetsAndReplies81.7 KB24.2 KB70 %23,0097,27468 %
/user/{userId}/tweets72.2 KB22.8 KB68 %20,4846,86666 %
/tweet/{tweetId}/quotes51.7 KB21.1 KB59 %14,0086,44454 %
/user/{userId}/media49.3 KB16.3 KB67 %14,8834,91967 %
/communities/{communityId}/tweets40.4 KB14.5 KB64 %11,2964,39261 %
/tweet/{tweetId}38.7 KB15.0 KB61 %10,6014,66956 %
/search34.3 KB14.4 KB58 %9,5824,55652 %
/user/{userId}/followers9.1 KB2.8 KB70 %2,27280165 %
/user/{userId}/articles8.6 KB3.3 KB62 %2,2631,00955 %

How this was measured (2026-10-11): one real captured response per endpoint (anonymized and shortened to a few items, so a full page with the default count is several times larger in both formats), converted with the same code the API runs. Both formats are minified JSON. Tokens are counted with OpenAI's o200k_base tokenizer (tiktoken, used by GPT-4o and later); with cl100k_base the median is 61 %. Claude and Gemini use their own tokenizers, so exact counts differ, but the reduction is of the same size because it comes from the fields that are left out.

The conversion runs on our side in about half a millisecond for a 300 KB timeline, so it does not add noticeable latency.

What the smaller responses mean in practice: context windows on Twitter/X data for AI agents, and storage for datasets on collecting X data for research.

When to use which

Use the simple format when you need the common fields and want code that does not break when X reshuffles its JSON. Use the raw format when you need something the simple format leaves out (cards, polls, edit history, community notes, X's ranking metadata): it has every field X sends.