Skip to main content

ID Conversion

Convert between X (formerly Twitter) usernames, numeric user IDs, and profile URLs. Three lightweight utility endpoints, one request each.
Note: For a fuller explainer on usernames, user IDs, and profile links, see Twitter ID Converter: Username, User ID, and Profile Link on the blog.
No-code option: for one-off conversions, use the free Sorsa ID Converter web tool. Paste a username, ID, or profile URL and get the result instantly, no API key required.

Why User IDs Matter

A username (handle) can be changed at any time, and once released, can be claimed by someone else. A numeric user ID is assigned at account creation and never changes. If you are building anything that stores or references X accounts, work with user IDs:
  • Handle changes do not break your system. The ID points to the same account regardless of how many times the user renames.
  • Indexes are faster. Integer keys outperform variable-length string keys.
  • Cross-time joins are clean. When matching accounts across datasets collected at different times, IDs are the only safe key.
  • Some Sorsa endpoints accept only IDs. /info-batch accepts user_ids, and several community and list endpoints reference accounts by numeric ID.

How User IDs Are Formatted

X uses Snowflake IDs: 64-bit integers that pack a timestamp, a machine ID, and a sequence number into a single value. Tweet IDs have used this format since 2010. User IDs are different. X kept assigning sequential integer IDs to accounts for years after Snowflake launched, and only migrated user IDs to the Snowflake format around 2020. This is why older accounts have short IDs (Jack Dorsey’s account is 12) while accounts created after 2020 have 19-digit IDs. The practical consequence: you cannot decode a creation timestamp from older user IDs. If you need a registration date for an older account, fetch the profile and read created_at.

Endpoint 1: Username to User ID

GET /v3/username-to-id/{user_handle}
Converts a handle (without @) into the permanent numeric user ID.
curl "https://api.sorsa.io/v3/username-to-id/elonmusk" \
  -H "ApiKey: YOUR_API_KEY"
{"id": "44196397"}
import requests

API_KEY = "YOUR_API_KEY"

def username_to_id(handle: str) -> str:
    resp = requests.get(
        f"https://api.sorsa.io/v3/username-to-id/{handle}",
        headers={"ApiKey": API_KEY},
    )
    resp.raise_for_status()
    return resp.json()["id"]

print(username_to_id("elonmusk"))  # "44196397"
async function usernameToId(handle) {
  const resp = await fetch(
    `https://api.sorsa.io/v3/username-to-id/${handle}`,
    { headers: { "ApiKey": "YOUR_API_KEY" } }
  );
  return (await resp.json()).id;
}

Endpoint 2: User ID to Username

GET /v3/id-to-username/{user_id}
Resolves a numeric user ID back to the account’s current handle. Useful for making stored IDs human-readable, or for detecting that an account has been renamed since you last looked.
curl "https://api.sorsa.io/v3/id-to-username/44196397" \
  -H "ApiKey: YOUR_API_KEY"
{"handle": "elonmusk"}
def id_to_username(user_id: str) -> str:
    resp = requests.get(
        f"https://api.sorsa.io/v3/id-to-username/{user_id}",
        headers={"ApiKey": API_KEY},
    )
    resp.raise_for_status()
    return resp.json()["handle"]
GET /v3/link-to-id?link={profile_url}
Extracts the permanent user ID from a full profile URL. Useful when processing links from spreadsheets, bookmarks, or scraped pages and you need to normalize them into IDs.
curl "https://api.sorsa.io/v3/link-to-id?link=https://x.com/elonmusk" \
  -H "ApiKey: YOUR_API_KEY"
{"id": "44196397"}
def link_to_id(profile_url: str) -> str:
    resp = requests.get(
        "https://api.sorsa.io/v3/link-to-id",
        headers={"ApiKey": API_KEY},
        params={"link": profile_url},
    )
    resp.raise_for_status()
    return resp.json()["id"]
Tip: if you need both the user ID and the full profile, skip the conversion step and call /info with the username parameter. It returns the complete profile (including id) in a single request. See Optimizing API Usage for more patterns like this.

Common Patterns

Batch Conversion

When you have a list of handles (CRM export, competitor list, spreadsheet) and need to convert them all to IDs:
import requests
import time

API_KEY = "YOUR_API_KEY"
BASE = "https://api.sorsa.io/v3"
HEADERS = {"ApiKey": API_KEY}


def batch_username_to_id(handles, pause=0.05):
    """
    Resolve a list of handles to user IDs.
    Returns: dict mapping handle -> id (or None if lookup failed).
    """
    results = {}
    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)
                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} -> {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. This is useful for refreshing a database that may have stale display names.

Normalizing Mixed Input

When users submit account references in different formats (some as handles, some as URLs, some as IDs), normalize everything to user IDs. The check below skips the API call entirely when the input is already an ID:
def normalize_to_id(value: str) -> str:
    """
    Accepts a handle, an @handle, a profile URL, or a numeric ID.
    Returns the numeric user ID.
    """
    value = value.strip().lstrip("@")

    if value.isdigit():
        return value

    if "x.com/" in value or "twitter.com/" in value:
        return link_to_id(value)

    return username_to_id(value)


# All four return the same ID
for source in ["elonmusk", "@elonmusk", "https://x.com/elonmusk", "44196397"]:
    print(normalize_to_id(source))
Use this as the first step in any ingestion pipeline so downstream logic always works with the stable identifier.

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:
def detect_renames(records):
    """
    records: list of {"user_id": str, "stored_handle": str}
    Returns: list of accounts that have renamed.
    """
    changes = []
    for record in records:
        try:
            current = id_to_username(record["user_id"])
        except requests.HTTPError:
            continue  # 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
For a richer 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 the account has renamed and when the most recent change happened.

Next Steps

  • Followers & Following: most follower-extraction workflows start with ID conversion.
  • Audience Geography: the /about endpoint accepts user_id and returns country data plus username change history.
  • Optimizing API Usage: avoid unnecessary conversion calls by using /info directly when you need the full profile.
  • API Reference: full specification for /username-to-id, /id-to-username, /link-to-id, and every other Sorsa endpoint.