Twitter (X) Username Lookup — A Developer's API Reference
Username → user object resolution is one of the most common API calls in any Twitter (X) workflow — enrichment pipelines, user-page rendering, ID-mapping for stored handles, competitor-tracking watchlists. This reference walks the single-lookup + batch patterns with runnable Python + honest cost math.
Pricing references URL-cited to each provider.
Why username lookup matters
X's internal ID for an account is a numeric string (like 44196397 for @elonmusk). Most API operations use the numeric ID; user-visible handles are strings. Anything that stores handles + queries data needs a resolver from handle → numeric ID + profile data.
Common use cases: user-profile page rendering, converting a handle list to IDs for subsequent API calls, enrichment (given handle, retrieve bio + follower count), watchlist validation (does this handle still exist? still verified?).
Single lookup — twitterapi.io
Auth via X-API-Key. Pass userName parameter. The call returns HTTP 200 either way: a handle that doesn't exist comes back as status: "error" in the JSON body, and a suspended account as data.unavailable: true.
Pricing per twitterapi.io/pricing: $0.00018 per lookup.
import os, requests
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"
def lookup(handle: str) -> dict | None:
r = requests.get(
f"{BASE}/twitter/user/info",
headers=HEADERS, params={"userName": handle}, timeout=10,
)
r.raise_for_status()
body = r.json()
data = body.get("data") or {}
if body.get("status") != "success" or data.get("unavailable"):
return None # not found, or suspended (data.get("unavailableReason"))
return data
user = lookup("nasa")
if user:
print(f"@{user['userName']} → id={user['id']}, followers={user['followers']:,}")
else:
print("handle not found or unavailable")Single lookup — X official
Auth via bearer token from X Developer Console.
Pricing per docs.x.com/x-api/getting-started/pricing: $0.010 per lookup.
# pip install tweepy
import tweepy
client = tweepy.Client(bearer_token="YOUR_X_BEARER")
def lookup_x(handle: str):
try:
user = client.get_user(username=handle, user_fields=["public_metrics", "description", "verified"])
return user.data
except tweepy.NotFound:
return None
u = lookup_x("nasa")
if u: print(f"@{u.username} → id={u.id}, followers={u.public_metrics['followers_count']:,}")Batch lookup — 100 handles at once
For workflows polling many usernames, single-lookup loop is inefficient. Batch endpoint patterns:
X official: /2/users/by?usernames=handle1,handle2,...,handle100 (up to 100 per call).
twitterapi.io: single-lookup loop; if you need batch performance, parallelize the calls (concurrent HTTP fine given per-key throughput headroom).
For very large watchlists (10K+ handles), parallel single-lookup at twitterapi.io still lands single-digit-dollars total. X official 100-per-batch reduces API-call count but per-user cost is unchanged.
# Batch lookup via concurrent single-calls (twitterapi.io)
import os, requests
from concurrent.futures import ThreadPoolExecutor
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
def lookup(handle: str) -> dict | None:
r = requests.get(
"https://api.twitterapi.io/twitter/user/info",
headers=HEADERS, params={"userName": handle}, timeout=10,
)
r.raise_for_status()
body = r.json()
data = body.get("data") or {}
if body.get("status") != "success" or data.get("unavailable"):
return None # not found, or suspended (data.get("unavailableReason"))
return data
HANDLES = ["nasa", "github", "vercel", "anthropic", "openai"] * 20 # 100 handles
with ThreadPoolExecutor(max_workers=10) as ex:
results = list(ex.map(lookup, HANDLES))
resolved = [r for r in results if r]
print(f"resolved {len(resolved)}/{len(HANDLES)}")
# Cost per twitterapi.io/pricing:
# 100 lookups × $0.00018 = $0.018 for the full batchSide-by-side — 2 lookup paths
Per-lookup cost ratio ~55× makes twitterapi.io the default for any polling workflow across many usernames.
Common lookup workflows
Watchlist validation: nightly cron pulls current status of N handles. Detect suspensions (data.unavailable) / deletions (status: "error") + verification changes.
ID resolution for downstream API calls: input is a handle list, downstream calls need numeric IDs. One lookup pass → build handle→ID map → downstream calls use IDs.
Enrichment for existing user records: your database has handles + you want current follower counts / bio. Batch lookup → merge back to your records.
Handle-exists check: before signup form validates or user-tagging feature accepts a handle, quick lookup confirms account exists.
Bot / spam detection: lookup + inspect followers / following / createdAt / description patterns. Very low follower + high following + new account + generic bio = likely spam.
Edge cases
Case sensitivity: handles are case-insensitive at lookup — @NASA, @nasa, @Nasa all resolve to the same account. The API returns the account's canonical case.
Renames: handles can change. Stored handles in your database may go stale. If lookup returns different id than expected for a known handle, the handle was reassigned to a new account (the old account changed handle or was deleted + handle re-registered).
Rate-limit on batches: parallel calls at 10-20 workers is safe for twitterapi.io. Higher can bump into per-key throughput ceilings.
Numeric-only handles: not supported — X handles are alphanumeric + underscore. If your input list has pure-numeric strings they're not valid handles.
Choosing a handle rather than resolving one: if your input list is a set of candidate usernames you are deciding between, the same lookup call doubles as an availability check. The guide to usernames for Twitter (X) covers X's handle rules, idea frameworks, and a script that classifies a candidate list as taken, suspended, or likely free.
# Practical example: watchlist validation + enrichment nightly cron.
import os, requests, json
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from datetime import datetime, timezone
HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"
WATCHLIST = Path("watchlist.txt").read_text().strip().split("\n") if Path("watchlist.txt").exists() else [
"nasa", "github", "vercel", "anthropic", "openai", "elonmusk",
]
def lookup(handle: str) -> tuple[str, dict | None]:
r = requests.get(f"{BASE}/twitter/user/info", headers=HEADERS, params={"userName": handle}, timeout=10)
r.raise_for_status()
body = r.json()
data = body.get("data") or {}
if body.get("status") != "success" or data.get("unavailable"):
return (handle, None) # not found, or suspended (data.get("unavailableReason"))
return (handle, data)
now = datetime.now(timezone.utc).isoformat()
results = []
with ThreadPoolExecutor(max_workers=10) as ex:
for handle, user in ex.map(lookup, WATCHLIST):
if user is None:
results.append({"captured_at": now, "handle": handle, "status": "NOT_FOUND_OR_UNAVAILABLE"})
else:
results.append({
"captured_at": now,
"handle": user["userName"],
"id": user.get("id"),
"followers": user.get("followers", 0),
"verified": user.get("isBlueVerified", False),
"status": "OK",
})
out = Path("watchlist_snapshot.jsonl")
with open(out, "a") as f:
for r in results:
f.write(json.dumps(r) + "\n")
n_ok = sum(1 for r in results if r["status"] == "OK")
n_missing = len(results) - n_ok
print(f"snapshot: {n_ok} live, {n_missing} not found or unavailable")
# Cost per twitterapi.io/pricing:
# len(WATCHLIST) × $0.00018 per snapshot
# Daily × 30 = ~$0.02-0.20/month for typical watchlistsQuestions readers ask
Can I look up multiple usernames in one call?
twitterapi.io: single-lookup per call; use concurrent parallel calls for batch. X official: /2/users/by supports up to 100 usernames per call. For most workloads, twitterapi.io concurrent still ends up cheaper + faster per completed lookup.
What happens if the account was suspended or deleted?
The API still returns HTTP 200 and reports it in the body: a deleted or renamed handle comes back as status: "error" with the message user not found, and a suspended account as status: "success" with data.unavailable: true and an unavailableReason. Handle both in your data with a None return or explicit 'not found' marker. For long-standing watchlists, periodically re-check — accounts get suspended, deleted, or renamed regularly.
How do I detect a handle rename?
Compare stored id vs returned id for the same handle over time. Same handle string with a different numeric ID = the handle was reassigned. The old account either changed handle or was deleted + the handle re-registered.
Can I do lookups without authentication?
Both providers require auth. twitterapi.io needs X-API-Key; X official needs bearer. No anonymous public API for user lookup exists in 2026.
What about looking up by numeric ID instead of username?
Both providers support ID-based lookup. twitterapi.io: /twitter/user/info?userId=<id>. X official: /2/users/<id>. Same pricing structure per provider.
Are usernames case-sensitive when I make the API call?
No — both APIs are case-insensitive at query. ?userName=NASA and ?userName=nasa resolve to the same account. The returned userName field uses the account's canonical case.
Continue
- Twitter (X) API — cluster hub
- Twitter (X) follower count API tutorial
- Twitter (X) profile analytics API guide
- Twitter (X) without an account — dev API
- twitterapi.io pricing
- Twitter (X) user search: find accounts by keyword
- Usernames for Twitter (X): rules, ideas, and how to check availability
- 01X Radar Explained: Twitter (X) Keyword Monitoring, Tiers, Limits and an API Alternative
- 02TweetDeck (X Pro) Alternatives in 2026: Columns, Monitoring and Scheduling for Twitter (X)
- 03Twitonomy Review: What the Twitter (X) Analytics Tool Still Does, and an API Alternative
- 04llms.txt for API docs, measured on two Twitter (X) API doc sets
- 05Botometer in 2026: What It Scored, Its Status, and a Live Alternative for Twitter (X)
- 06Twitter (X) Tweet Metadata — API Fields Reference
- 07Twitter (X) Competitor Analysis — A Developer's API Guide
- 08Twitter (X) Viewer API — A Developer's Read-Only Guide
Stop reading. Start building.
Starter credits cover real testing on real data. Google sign-in, no card, no application queue.
Get an API key