7-day offer$199$9
X-Autopilot

X API user lookup: six routes and a $1 request

Six routes return an X user profile, the live spec exposes 27 user fields, and X's own integration guide documents 11 of them with three dead parameter names.

X-Autopilot Team··10 min read
On this page · 10 sections

The short version

  • ▸Six routes return a user profile in API version 2.174: by id, by ids, by username, by usernames, the authenticated user, and user search. X's User Lookup hub lists five of them and leaves search out, because search has its own hub page nobody links to from the first one.
  • ▸The live spec exposes 27 values in user.fields. The integration guide's field accordion documents 11, and one of the 11 is pinned_tweet_id, which is not in the enum at all. Fields like connection_status, receives_your_dm and active_follower_count exist and appear in no guide.
  • ▸Every published example in the lookup guide uses three names the spec dropped: pinned_tweet_id, tweet.fields, and an includes.tweets response key. The generated Python SDK agrees with the spec, not the guide. The string pinned_tweet_id appears zero times in xdk 0.10.6.
  • ▸User reads bill at $0.010 per user returned, the joint-highest read rate on the card. A 100-id batch is $1.00 per request. User search accepts max_results up to 1,000, so a single search request can return $10 of billable users.
  • ▸GET /2/users/me is not on the Owned Reads table, so reading your own profile costs the full $0.010 instead of the $0.001 owned rate. It is also one of two lookup routes that reject an app-only Bearer token, and the only published user-search example uses one.

Quick answer

Six routes in X API v2 return a user profile, and the difference between them is money, not convenience. Reads bill at $0.010 per user returned, so a 100-handle batch is $1.00 a request and a user search set to max_results=1000 can return ten dollars of billable users in one call. The bigger problem is that X's hand-written integration guide has fallen three renames behind its own API description: every example it publishes uses pinned_tweet_id, tweet.fields and an includes.tweets response key, none of which exist in spec version 2.174.

Last updated: November 2026

Every claim below is checked against the live API description at api.x.com/2/openapi.json, version 2.174, X's own lookup, search, pricing and rate-limit pages, and the published Python SDK wheel. All read on 9 November 2026.

Six routes, and the hub page lists five

Here is the full set, with what each one actually takes.

RouteInputCapApp-only token
GET /2/users/{id}one numeric id1yes
GET /2/users?ids=numeric ids100yes
GET /2/users/by/username/{username}one handle1yes
GET /2/users/by?usernames=handles100yes
GET /2/users/methe token1no
GET /2/users/search?query=a keyword1,000 per pageno

X's User Lookup hub prints the first five in a table and never mentions the sixth. User search lives on a separate hub page that the lookup page does not link, and the lookup page is the one that ranks. That is the whole reason "how do I search X users by keyword" gets answered on reseller blogs instead of by X.

Next, look at the token column. Four routes list BearerToken in their security block, so app-only auth works. /2/users/me and /2/users/search do not, and the guide is explicit about /2/users/me in a warning box. It is silent about search, where the single published example request is this:

curl "https://api.x.com/2/users/search?query=python%20developer&user.fields=description,verified,public_metrics" \
  -H "Authorization: Bearer $BEARER_TOKEN"

An app-only Bearer token is not in that route's security block. If you copied the one example X ships for user search, you copied an auth mode the spec says the endpoint does not take. Use a user access token from the OAuth 2.0 PKCE flow instead, as covered in how to get an X API key.

27 fields in the spec, 11 in the guide

By default a user lookup returns three keys: id, name, username. Everything else is opt-in through user.fields, and the enum in spec 2.174 holds 27 values:

active_follower_count, confirmed_email, connection_status, created_at, description, entities, id, is_identity_verified, location, name, parody, profile_banner_url, profile_image_url, protected, public_metrics, receives_your_chat, receives_your_dm, subscriber_count, subscribes_to_you, subscription, subscription_type, url, username, verified, verified_followers_count, verified_type, withheld.

X's integration guide documents eleven in its field accordion: created_at, description, entities, location, pinned_tweet_id, profile_image_url, protected, public_metrics, url, verified, withheld.

Ten of those eleven are real. The eleventh, pinned_tweet_id, is not in the enum. So the documented surface is ten of twenty-seven, and the missing seventeen include every field added since the platform stopped being called Twitter.

The fields worth paying for

Three of the undocumented ones change what you can build.

connection_status returns an array drawn from six values: blocking, follow_request_received, follow_request_sent, followed_by, following, muting. One lookup with a user token tells you whether an account follows you back. Without it you page a follower list at $0.010 a head, which is the expensive way to learn the same fact.

receives_your_dm is a boolean with a caveat written into the spec: it is omitted when the DM permission cannot be determined, so it is never reported as false in that case. Absent does not mean closed. A pipeline that treats a missing key as "cannot DM" will silently skip reachable people. The companion receives_your_chat is a string instead of a boolean and takes three values: encrypted, unencrypted, or none.

active_follower_count is the number of your followers who were active on X in the last 24 hours. The spec adds three conditions: it is returned only for the authenticated user with account analytics enabled, it is omitted entirely for accounts with 50 or fewer followers, and it is an estimate once an account passes 20,000 followers. That is a real engagement denominator, and it is in no guide page.

There is also confirmed_email, typed as a plain string with no description whatsoever. It is the only field in the enum X has not written a sentence about.

Three dead names in every published example

This is the part that costs people an afternoon. The guide's worked example reads:

curl "https://api.x.com/2/users/by/username/XDevelopers?user.fields=created_at,description,public_metrics,verified&expansions=pinned_tweet_id&tweet.fields=created_at,public_metrics" \
  -H "Authorization: Bearer $BEARER_TOKEN"

Against spec 2.174, three things in that line are stale.

  1. expansions=pinned_tweet_id. The expansions enum on every user route now holds exactly three values: affiliation, most_recent_post_id, pinned_post_id.
  2. tweet.fields. The companion group is post.fields, with 31 values. tweet.fields is not a declared parameter on any user route.
  3. includes.tweets in the sample response. The Expansions object X returns is keyed calls, media, places, polls, posts, topics, users. There is no tweets key.

The same guide's sample response also shows public_metrics.tweet_count. UserPublicMetrics requires four keys, and they are followers_count, following_count, post_count and listed_count, plus optional like_count and media_count. tweet_count is not among them. In the whole live spec, the only object that still carries tweet_count is Trend.

Which one is right? The SDK settles it, because it is generated from the same description the docs site renders. In xdk 0.10.6 for Python, published 25 July 2026, the string pinned_tweet_id appears zero times, UserPublicMetrics declares post_count as required with no tweet_count anywhere, and the expansion fields are pinned_post_id and most_recent_post_id. The generated client and the spec agree. The hand-written guide is the outlier. If you are picking a source to trust, the X API Python SDK and the raw description beat the prose.

One asymmetry to keep in mind while renaming things: active_follower_count appears zero times in that same wheel. The field is newer than July 2026, so a typed SDK model will not expose it even though the API returns it. The Python models set extra="allow", so the value arrives, just not as an attribute you can autocomplete. Same pattern as the field-group churn covered in X API expansions.

What a lookup actually costs

User reads sit at $0.010 per resource on the pay-per-use card. Only DM Event: Read matches it. Posts are $0.005, and likes, mutes and blocks are $0.001. Profiles are the single most expensive thing you can read ten thousand of.

The word that matters is resource, not request:

CallUsers returnedCost
GET /2/users/by/username/jack1$0.01
GET /2/users/by?usernames= with 100 handles100$1.00
GET /2/users/search?query=...&max_results=100up to 100up to $1.00
GET /2/users/search?query=...&max_results=1000up to 1,000up to $10.00

Batching does not save you anything on a per-resource rate card. It saves rate-limit budget, which is a different scarce thing. A thousand single lookups and ten 100-id batches cost the same $10; the batches just use ten requests out of your 300 instead of a thousand.

Two details cut the other way. Resources are deduplicated within a 24-hour UTC day window, so re-reading the same profile inside one day is free, and X calls that a soft guarantee rather than a promise. And the monthly cap X publishes is 3 million Post reads per billing cycle. There is no published user-read cap, and GET /2/usage/tweets, the only programmatic usage endpoint, reports Post consumption. Your profile spend is auditable in the Developer Console and nowhere else. The same blind spot shows up across the rate card in X API pricing.

The Owned Reads gap

Owned Reads drop to $0.001 per resource when the {id} in the path is the authenticated user and that user owns the app. Twelve endpoints qualify, and every one of them is a /2/users/{id}/something route: your posts, mentions, liked posts, bookmarks, followers, following, blocking, muting, and four list routes.

GET /2/users/me is not on that list. Neither is GET /2/users/{id}. So reading your own profile, which is as owned as data gets, bills at the full $0.010. Ten times the owned rate for the one object you certainly have the right to read.

Rate limits: the 75 that is easy to miss

RoutePer appPer user
/2/users, /2/users/{id}, /2/users/by, /2/users/by/username/{username}300/15min900/15min
/2/users/search300/15min900/15min
/2/users/meno app row75/15min

That 75 is the one that bites. A service that calls /2/users/me on every page load to resolve the signed-in account will hit a 429 at modest traffic, and because the route takes no app-only token there is no app-wide pool to fall back on. Resolve the id once at login, cache it, and use /2/users/{id} after that, where you get 900 per user and 300 per app. More of these asymmetries are mapped in X API rate limits.

Worth noting for anyone counting: the rate-limit page heads this section "Users (14 endpoints)" and the rows under it do add to fourteen. What is absent is GET /2/users/{id}/affiliates and the two /2/users/me/followers and /2/users/me/following routes, which exist in the spec with no rate-limit row anywhere.

Failure modes you should code for

Suspended and deleted accounts behave differently depending on which route you used. A single-user lookup returns 404. A batch lookup returns 200 with the surviving users in data and an errors array naming each missing one by resource_id and resource_type. That is a partial success, so a client that only branches on the status code will treat a half-empty batch as complete.

Protected accounts still return the basics. You get id, name, username and protected: true for an account you do not follow; the pinned post expansion may be withheld.

Handles are not stable identifiers. The username route is convenient and the id route is correct. Store numeric ids, because a handle change silently moves to a different account while the id never does.

When the API is the wrong tool

If you are enriching a CRM, scoring a lead list, or building a dashboard over profiles you already have ids for, these endpoints are exactly right and the per-resource math is predictable once you accept that a batch is not a discount.

If what you want is to watch a feed, reply in your own voice, and post on a schedule from your own account, user lookup is a strange place to start. You end up paying $0.010 a profile to rebuild a view X already renders for free in a browser, and the write side is where the API gets restrictive rather than merely expensive. X-Autopilot takes the browser route instead: it drives your own logged-in session on your own Mac, so your session cookie never leaves the machine and there is no cloud IP pool or delegate account in the path. That is a lower detection surface than a cloud scheduler, not a safe one, and automating engagement through a browser is a gray area that X's automation rules address directly. The API route is the sanctioned one. Pick it when you need sanctioned access, and price it before you build.

Frequently asked

Answers indexed by Google + AI assistants.

How do I look up a user with the X API?+

GET https://api.x.com/2/users/by/username/{username} for a single handle, or GET /2/users/by?usernames=a,b,c for up to 100 at once. If you already hold numeric ids, use GET /2/users/{id} or GET /2/users?ids=. Add user.fields to get anything beyond id, name and username, because the default response is those three keys and nothing else. Four of the six routes accept an app-only Bearer token; /2/users/me and /2/users/search require a user token.

What user fields can the X API return?+

Twenty-seven, as of spec version 2.174: active_follower_count, confirmed_email, connection_status, created_at, description, entities, id, is_identity_verified, location, name, parody, profile_banner_url, profile_image_url, protected, public_metrics, receives_your_chat, receives_your_dm, subscriber_count, subscribes_to_you, subscription, subscription_type, url, username, verified, verified_followers_count, verified_type and withheld. The pinned post and most recent post arrive through expansions, not through user.fields.

Why does expansions=pinned_tweet_id not work?+

Because the expansions enum on every user route holds exactly three values now: affiliation, most_recent_post_id and pinned_post_id. The companion field group is post.fields, not tweet.fields, and the expanded object comes back under includes.posts, not includes.tweets. X's integration guide still shows the old names in its cURL, Python and JavaScript examples and in its sample response.

How much does an X API user lookup cost?+

User: Read is $0.010 per resource returned, and resource means user, not request. A single-handle lookup is a cent. A 100-username batch is $1.00. A user search with max_results=1000 returns up to 1,000 billable users in one call. Resources are deduplicated inside a 24-hour UTC window, which X calls a soft guarantee rather than a promise.

Is GET /2/users/me an Owned Read?+

No. The Owned Reads table lists twelve endpoints that drop to $0.001 per resource when the id matches the authenticated app owner, and all twelve are /2/users/{id}/something routes. Neither /2/users/me nor /2/users/{id} appears on it, so fetching your own profile bills at the standard $0.010 User: Read rate, ten times the owned rate for data that is definitionally yours.

What does connection_status return?+

An array of strings drawn from six values: blocking, follow_request_received, follow_request_sent, followed_by, following and muting. It describes the relationship between the authenticated user and the user being looked up, which makes it the cheapest way to answer do they follow me back without paging a follower list. It needs a user token, and no guide page documents it.

Sources
Related searches
x api user lookupx api get user by username/2/users/by/usernamex api user fieldstwitter api user lookup 2026x api users/mex api user search endpointx api profile lookup costx api pricingx api rate limitsx api expansionsx api keytwitter api v2 user objectx api pythonuser public metrics
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.
7-day offer: $199 $9 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.