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. Returns 404 if the handle doesn't exist.
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,
)
if r.status_code == 404: return None
r.raise_for_status()
return r.json()
user = lookup("nasa")
if user:
print(f"@{user['userName']} → id={user['id']}, followers={user['followers_count']:,}")
else:
print("handle not found")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,
)
if r.status_code == 404: return None
r.raise_for_status()
return r.json()
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 / deletions (404) + 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 follower_count / following_count / created_at / 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.
# 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)
if r.status_code == 404: return (handle, None)
r.raise_for_status()
return (handle, r.json())
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"})
else:
results.append({
"captured_at": now,
"handle": user["userName"],
"id": user.get("id"),
"followers_count": user.get("followers_count", 0),
"verified": user.get("verified", 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")
# 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?
API returns 404. Handle 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=. X official: /2/users/. 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
Stop reading. Start building.
Starter credits cover real testing on real data. Google sign-in, no card, no application queue.
Get an API key