Updated June 2026: corrected the date X switched user IDs to the Snowflake format (February 1, 2016, per X's own 64-bit migration announcement) and re-verified the official pay-per-use read rates.
Key Takeaway: A Twitter (X) user ID is a permanent 64-bit number assigned at account creation, while the @username can change at any time. To convert between them, send the handle, numeric ID, or profile URL to a lookup endpoint. Store the ID, not the handle, since the ID survives renames.
If you have ever stored @brand_name as a primary key and then watched that account rebrand to @new_brand, you already know why this matters. X lets users change a handle whenever they want, and once @brand_name is released, anyone can claim it. The numeric user ID is the only identifier that stays attached to the original account for life.
For doing these conversions in code, Sorsa API, an alternative Twitter/X API provider, exposes three dedicated endpoints (handle to ID, ID to handle, profile URL to ID) behind a single API key, with no OAuth flow and no app-approval queue. Each conversion counts as one flat request instead of the per-resource metering the official API uses, so resolving 100,000 handles runs about $199 on the Pro plan against roughly $1,000 in official X API credits. For one-off lookups where you do not want to write any code, the free ID Converter playground does all three conversions in the browser with no key at all.
Disclosure: Sorsa API is our product. The pricing comparisons below use the official X API's published pay-per-use rates, verified in June 2026. Check current rates against your own workload before committing.
This guide is the developer's path through the topic: when you actually need to convert, why the design of these identifiers matters, how to do each conversion in code, and how to run it across thousands of accounts without your pipeline breaking.
Table of Contents
- Why user IDs are the only identifier you can trust
- How Twitter user IDs are generated
- How to find a Twitter user ID
- The three conversion operations
- How much does ID conversion cost in 2026?
- Converting with the Sorsa API
- Batch conversion at scale
- Normalizing mixed input
- Detecting handle changes
- In practice
- Getting started
- Frequently asked questions
Why user IDs are the only identifier you can trust
A username on X is mutable. A user can change @old_name to @new_name today, and tomorrow someone else can grab @old_name. If an application treats the handle as a primary key, two things break at once: every stored reference points to the wrong account, and a third party may now hold the handle you used to associate with the original user.
This is not a rare edge case. A longitudinal study that monitored 8.7 million accounts found that around 10% of Twitter users change their handle over time (Jain and Kumaraguru, IIIT-Delhi). In our own work helping companies move off the official X API after the 2023 pricing overhaul, handle churn was one of the most common silent data-corruption issues we found. Several clients ran monitoring dashboards that stored handles as foreign keys, and when a tracked competitor rebranded, the dashboard kept happily collecting tweets from a completely unrelated account that had picked up the old handle.
The user ID is a 64-bit integer assigned at account creation. It cannot be changed, cannot be transferred, is unique across the entire platform, and is the join key every reliable X data pipeline is built on:
- Handle changes do not break your system. Whether an account renames once or fifty times, the ID points to the same profile.
- Indexes are faster. Integer indexing is more storage-efficient than variable-length strings, and lookups by numeric primary key beat lookups by username at every scale.
- Cross-time joins stay clean. When matching accounts across datasets collected at different times, IDs are the only safe key. A handle match across years can silently match two different people.
- Several endpoints require IDs. Sorsa's
/info-batchacceptsuser_ids, list and community endpoints reference accounts by numeric ID, and most official X API endpoints likewise expect numeric IDs.
If you take one thing from this article: store IDs in your database, not handles. Treat the handle as cached display data that can go stale.
Store the ID as a string, not an integer
Store and transport Twitter user IDs as strings, even though they look like numbers. Modern 19-digit Snowflake IDs exceed JavaScript's safe integer limit (Number.MAX_SAFE_INTEGER, which is 2^53 - 1, or 9,007,199,254,740,991), so parsing an ID into a native JS number silently rounds the last few digits and corrupts it. The same risk shows up anywhere a 64-bit value lands in a 32-bit or float type. Keep IDs in string columns or a 64-bit BIGINT, never a default int or a JavaScript number. The Sorsa API returns every ID as a JSON string for exactly this reason.
How Twitter user IDs are generated
Twitter built its ID generation system, Snowflake, in 2010 to solve a distributed-systems problem. At Twitter scale, asking a central database "what is the next ID?" for every new tweet, message, or account would not work. Snowflake lets any server mint globally unique 64-bit integers independently, with no coordination, by combining three values:
- Timestamp (41 bits): milliseconds since the Twitter epoch (
2010-11-04 01:42:54.657 UTC). - Worker / machine ID (10 bits): identifies the server that generated the ID.
- Sequence number (12 bits): increments within the same millisecond, supporting up to 4,096 IDs per machine per millisecond.
Add a sign bit and you have 64 bits total. Because the timestamp sits in the most significant bits, Snowflake IDs are roughly sortable by time, which is why tweet IDs are time-ordered when you compare them.
The user ID detail most converter pages get wrong
Here is a point most ID-converter pages state incorrectly: tweet IDs went Snowflake in 2010, but X did not switch user IDs to the Snowflake format until February 2016, and plenty of pages still repeat a wrong date (often 2020). Per X's own 64-bit ID migration announcement, Snowflake IDs began rolling out to users, lists, and saved searches on February 1, 2016. Before that, X handed out short, sequential integer user IDs for years. That is why Jack Dorsey's account is 12, and why accounts created before the switch have short, low IDs while accounts created after it have long, 18 to 19 digit Snowflake IDs.
The practical consequence: you can extract a creation timestamp from any tweet ID, but you cannot reliably extract a registration date from an old user ID, because pre-2016 user IDs carry no embedded timestamp. If you need a join date for an older account, read the created_at field from the profile directly, or use the account age checker for a quick one-off lookup.
How to find a Twitter user ID
X does not display a user ID anywhere in its interface, so you have to derive it. There are three practical paths, in rough order of effort:
The manual way (slow). Open the profile, view the page source (Ctrl+U or Cmd+U), and search the HTML for user_id or rest_id. Or open browser dev tools (F12), watch the Network tab, refresh, and read the ID out of an API response. Both work for a single account, but they are tedious, they break whenever X reshuffles its markup, and they do not scale past a handful of lookups.
A free converter (one-off). Paste a handle, ID, or profile URL into a lookup tool and read the result. The Sorsa ID Converter tool handles all three directions in the browser with no signup and no API key, and it also returns the profile so you can confirm you have the right account. Suspended, deleted, or never-existing accounts return a not-found result; protected (private) accounts return the ID but limited stats.
The API (at scale). When you need to resolve more than a few accounts, or run the lookup unattended inside a pipeline, call a conversion endpoint directly. The code below shows all three operations in Python.
The three conversion operations
There are exactly three conversion operations you ever need:
| You have | You want | What to do |
|---|---|---|
Username (handle, with or without @) | Numeric user ID | Resolve handle to ID |
| Numeric user ID | Current username | Resolve ID to handle |
Profile URL (x.com/username or twitter.com/username) | Numeric user ID | Extract handle from URL, resolve to ID |
Each operation is a single API call. None of them require OAuth, callback URLs, or app approval when you use a third-party API like Sorsa. A third-party key replaces the official developer-account flow entirely, so there is no application to submit and nothing to wait on, the same path covered in our guide to using the X API without a developer account. The Python and curl examples for all three are in the code section below, and each maps to a documented endpoint: username to ID, ID to username, and profile link to ID.
There is no equivalent operation for tweets. A tweet ID is visible directly in the tweet URL (x.com/user/status/1234567890), so you parse it out of the URL string rather than calling a lookup.
When to use the /info endpoint instead
If you need both the user ID and the full profile (display name, follower count, bio, avatar URL), do not call /username-to-id and then /info separately. Call /info?username=... directly: it returns the complete profile, including the id field, in a single request. Splitting it into two calls is one of the most common cost mistakes we see in code reviews. The optimizing API usage guide covers more patterns like this.
How much does ID conversion cost in 2026?
Pricing shifted significantly this year. In February 2026 X moved its API to a pay-per-use credit model as the default for new developers, replacing the old Basic and Pro tiers. A username-to-ID lookup on the official X API is a user read, billed at about $0.010 per resource returned. For an occasional lookup inside an internal tool, that is fine. For batch or recurring jobs the cost adds up fast: resolving 100,000 handles costs roughly $1,000 in official API credits.
On Sorsa, each of the three conversion endpoints counts as one flat request, regardless of plan:
| Sorsa Starter | Sorsa Pro | Official X API (pay-per-use) | |
|---|---|---|---|
| Billing unit | 1 request (flat) | 1 request (flat) | per resource returned |
| Cost per conversion | $0.0049 | $0.00199 | ~$0.010 (user read) |
| Monthly requests included | 10,000 | 100,000 | none, buy credits |
| 100,000 conversions | use the Pro plan | $199/month flat | ~$1,000 in credits |
| Authentication | API key header | API key header | OAuth 2.0 |
| Setup | ~30 seconds | ~30 seconds | app plus credit purchase |
Per conversion, the Pro plan works out to about 5x cheaper than the official API, and the gap widens if you batch: profile lookups through /info-batch bundle up to 100 accounts into a single request. For the full picture of how the official model changed this year, see our Twitter API pricing breakdown and the reasons behind why the official API is so expensive. Plan details for our side are on the pricing page.
One honest caveat on the official side: its cheapest read tier, owned reads at $0.001 per resource, is genuinely competitive, but it only applies when an app reads its own account's data through a fixed set of endpoints. Handle-to-ID lookups for arbitrary third-party accounts are non-owned reads billed at the standard user-read rate, so the flat per-request model stays ahead for the conversion work this guide covers.
Converting with the Sorsa API
Authentication is a single header: ApiKey: YOUR_API_KEY. No OAuth flow, no scopes, no callback. Full details are on the authentication page, and the ID conversion reference documents all three endpoints together.
import requests
API_KEY = "YOUR_API_KEY"
BASE = "https://api.sorsa.io/v3"
HEADERS = {"ApiKey": API_KEY}
def username_to_id(handle: str) -> str:
"""Resolve a handle (without @) to its permanent numeric user ID."""
resp = requests.get(f"{BASE}/username-to-id/{handle}", headers=HEADERS)
resp.raise_for_status()
return resp.json()["id"] # returned as a string, keep it that way
def id_to_username(user_id: str) -> str:
"""Resolve a numeric user ID to the account's current handle."""
resp = requests.get(f"{BASE}/id-to-username/{user_id}", headers=HEADERS)
resp.raise_for_status()
return resp.json()["handle"]
def link_to_id(profile_url: str) -> str:
"""Extract the numeric user ID from a full profile URL."""
resp = requests.get(
f"{BASE}/link-to-id",
headers=HEADERS,
params={"link": profile_url},
)
resp.raise_for_status()
return resp.json()["id"]
# Examples
print(username_to_id("elonmusk")) # "44196397"
print(id_to_username("44196397")) # "elonmusk"
print(link_to_id("https://x.com/elonmusk")) # "44196397"
For a curl equivalent of the first call:
curl "https://api.sorsa.io/v3/username-to-id/elonmusk" \
-H "ApiKey: YOUR_API_KEY"
# {"id": "44196397"}
Batch conversion at scale
When you have a spreadsheet of competitor handles, a CRM export, or a list of accounts to seed a monitoring pipeline, you need to convert them all to IDs in one pass. The pattern below adds retry logic, soft rate limiting (Sorsa allows 20 requests per second per key on every plan), and graceful handling of suspended or deleted accounts. For broader coverage of pagination, batching, and error handling across the rest of the API in Python, see the Twitter API Python guide.
import requests
import time
from typing import Iterable
API_KEY = "YOUR_API_KEY"
BASE = "https://api.sorsa.io/v3"
HEADERS = {"ApiKey": API_KEY}
def batch_username_to_id(handles: Iterable[str], pause: float = 0.05) -> dict[str, str | None]:
"""
Resolve a list of handles to user IDs.
Returns a dict mapping handle -> id (or None if the lookup failed).
Soft rate limit: ~20 req/s, well under Sorsa's per-key cap.
"""
results: dict[str, str | None] = {}
for handle in handles:
handle = handle.strip().lstrip("@")
try:
resp = requests.get(f"{BASE}/username-to-id/{handle}", headers=HEADERS, timeout=10)
if resp.status_code == 200:
results[handle] = resp.json()["id"]
elif resp.status_code == 404:
results[handle] = None # Account does not exist or is suspended
elif resp.status_code == 429:
time.sleep(1) # Back off and retry once
retry = requests.get(f"{BASE}/username-to-id/{handle}", headers=HEADERS, timeout=10)
results[handle] = retry.json()["id"] if retry.status_code == 200 else None
else:
results[handle] = None
except requests.RequestException:
results[handle] = None
time.sleep(pause)
return results
handles = ["NASA", "SpaceX", "Tesla", "OpenAI", "stripe"]
id_map = batch_username_to_id(handles)
for handle, uid in id_map.items():
print(f"@{handle:<10} -> {uid or '(not found)'}")
The reverse direction works the same way. Swap the URL for /id-to-username/{user_id} and read the handle field from the response.
Normalizing mixed input
A common situation: your application accepts account references from end users or upstream systems in whatever format the source happens to produce. Some entries are bare handles, some are @-prefixed, some are full URLs, some are already numeric IDs. The normalizer below collapses all four shapes into a stable user ID, and skips the API call entirely when the input is already an ID.
def normalize_to_id(value: str) -> str:
"""
Accept any of:
- "elonmusk"
- "@elonmusk"
- "https://x.com/elonmusk"
- "https://twitter.com/elonmusk"
- "44196397"
Returns the numeric user ID as a string.
"""
value = value.strip().lstrip("@")
# Already a numeric ID, no API call needed
if value.isdigit():
return value
# Profile URL
if "x.com/" in value or "twitter.com/" in value:
return link_to_id(value)
# Bare username
return username_to_id(value)
# All four return the same ID
for source in ["elonmusk", "@elonmusk", "https://x.com/elonmusk", "44196397"]:
print(f"{source:<35} -> {normalize_to_id(source)}")
Use this as the first step in any ingestion pipeline. It guarantees that downstream logic always works with the stable identifier, regardless of how messy the input is.
Detecting handle changes
If you stored both the user ID and the handle at collection time, you can periodically re-resolve the IDs and detect which accounts have renamed. This is the workflow for refreshing a database that may hold stale display names.
def detect_renames(records: list[dict]) -> list[dict]:
"""
records: list of {"user_id": str, "stored_handle": str}
Returns: list of {"user_id", "old_handle", "new_handle"} for renamed accounts.
"""
changes = []
for record in records:
try:
current = id_to_username(record["user_id"])
except requests.HTTPError:
continue # Account deleted, suspended, or transient error
if current and current.lower() != record["stored_handle"].lower():
changes.append({
"user_id": record["user_id"],
"old_handle": record["stored_handle"],
"new_handle": current,
})
time.sleep(0.05)
return changes
db_records = [
{"user_id": "44196397", "stored_handle": "elonmusk"},
{"user_id": "1234567890", "stored_handle": "old_brand_name"},
]
for change in detect_renames(db_records):
print(f"Renamed: @{change['old_handle']} -> @{change['new_handle']} (ID {change['user_id']})")
For a richer rename audit, the /about endpoint returns username_change_count and last_username_change_at. That tells you not just the current handle but how many times an account has renamed and when the most recent change happened.
In practice
A roughly 12-person social analytics team came to us after a competitor they were tracking rebranded, and their dashboard quietly ingested a stranger's tweets for almost three weeks before anyone noticed. The root cause was the classic one: they had stored handles as keys. The fix was structural rather than clever. Resolve every handle to its user ID on ingest, store the ID as the key, keep the handle as display-only, and run a scheduled re-resolve to catch renames (the detect_renames pattern above).
The cost side mattered too. Their monthly handle-resolution volume had been running near $1,000 in official X API user-read credits. On the Pro plan at $199 flat for 100,000 requests it became a predictable line item, roughly a 5x reduction on that workload, with extra headroom because bulk profile lookups through /info-batch pack up to 100 accounts into one request. The reliability win, no more silent cross-account corruption, was the part they cared about most.
Getting started
Three ways to try this:
- No-code, no signup. Open the ID Converter playground and paste any handle, ID, or profile URL. Useful for one-off lookups and for confirming an account exists before you write any code.
- API key. Sign up at api.sorsa.io, drop your key into the
ApiKeyheader, and the Python examples above run as-is. The Starter plan is $49/month for 10,000 requests, enough for most batch conversion jobs, and the flat 20 requests per second applies on every plan. - Already on the official X API? The migration guide maps each official endpoint to its Sorsa equivalent, and our overview of Twitter API alternatives covers the tradeoffs.
The full API reference documents every parameter and response field for the three conversion endpoints here, plus the rest of the 40-endpoint API.
Frequently asked questions
What is a Twitter (X) user ID?
A Twitter user ID is a unique 64-bit integer assigned to an X account when it is created. It is permanent: it cannot be changed, transferred, or reassigned. Unlike the username (handle), which can be edited at any time, the user ID stays attached to the same account for the lifetime of that account, which is why developers use it as the stable key for any account reference.
How do you find your own Twitter user ID?
The X interface does not display your user ID anywhere. To find it, paste your handle or profile URL into a converter such as the Sorsa ID Converter, or send your handle to a lookup endpoint. You can also download your X data archive from account settings, which includes your user ID in the account metadata.
Can a Twitter user ID change?
No. A Twitter user ID is assigned at account creation and is permanent for the life of the account. Renaming the account, changing the display name, switching email addresses, or being suspended and then reinstated does not change the user ID. The only thing that changes is the link between a handle and an ID, which shifts when the handle itself is changed and the old handle becomes available for someone else to claim.
Why are some Twitter user IDs short and others very long?
X assigned short, sequential integer IDs to early accounts, which is why Jack Dorsey's account is 12, and only switched user IDs to the 64-bit Snowflake format in February 2016. Accounts created before that switch have short, low IDs. Accounts created after it have long Snowflake IDs, around 18 to 19 digits, that encode a creation timestamp in the upper bits.
Can you decode a registration date from a user ID?
For accounts created after the Snowflake switch in February 2016, yes: shift the ID right by 22 bits, add the Twitter epoch (1288834974657), and you get the creation timestamp in milliseconds. For older accounts with sequential pre-2016 IDs, no, because those IDs carry no embedded timestamp. In that case fetch the profile and read the created_at field directly.
Is there a free Twitter ID converter that does not require an API key?
Yes. The Sorsa ID Converter playground handles all three operations (handle to ID, ID to handle, profile URL to ID) in the browser with no API key and no signup, and it returns the profile so you can confirm the account. It is built for one-off lookups. For batch jobs, recurring pipelines, or anything that runs unattended, an API key is the better fit.
How can developers convert thousands of usernames to IDs at scale?
For high-volume work, the Sorsa API exposes three conversion endpoints (/username-to-id, /id-to-username, /link-to-id) at a flat one request per conversion. On the Pro plan at $199/month for 100,000 requests, each conversion is about $0.002, roughly 5x cheaper than the official API's per-resource rate. Throughput is capped at 20 requests per second per key, high enough that your own code is usually the bottleneck, not the API.
What is the difference between a user ID and a tweet ID?
Both are 64-bit Snowflake-style integers, but they live in separate namespaces and identify different things. A user ID identifies an account; a tweet ID (also called a status ID) identifies a single post. Tweet IDs are visible directly in the tweet URL (x.com/user/status/{tweet_id}), so no lookup is needed for them. User IDs are not exposed anywhere in the X interface, which is why dedicated conversion endpoints exist.
Reviewed by Keksich, founder of Sorsa, marketer and X API researcher.
How this guide was put together: it draws on our hands-on work building and operating an alternative Twitter/X API and on direct testing against our live conversion endpoints, cross-checked against the Sorsa API documentation for endpoint behavior and the official X API pricing announcement for the current pay-per-use rates. The Snowflake structure is sourced from Twitter's original engineering post, the February 2016 user-ID switch from X's 64-bit ID migration announcement, and the username-churn figure from Jain and Kumaraguru's longitudinal study. Pricing and rate figures verified June 2026. More about who we are is on our about page.