Instagram User ID API: Username to ID Lookup Guide (2026)
Instagram user ID API tutorial: convert usernames to numeric IDs and IDs back to usernames with SandBase, with curl and Python code and real responses tested 2026-10-01 to 2026-10-04.

Update (2026-10-04): The ID → username step now uses
instagram/v3/user-id-to-username. On 2026-10-01 that endpoint was intermittent (2 of 4 re-test calls returned HTTP 503), so earlier versions of this post usedinstagram/v1/user-info-by-idinstead. On 2026-10-04 it resolved 54 of 54 calls for 18 brand IDs in 2.5 to 4.9 seconds each in the Instagram ID to username tests, and in my re-runs of this post’s code the same day (English and Chinese versions) it answered all 31 calls for real accounts in 2.5 to 5.0 seconds.instagram/v1/user-id-to-usernameandinstagram/v2/user-id-to-usernameare disabled in the SandBase registry and returned HTTP 404endpoint_not_foundon 2026-10-04. The Endpoint 2, follower-tracker and batch code was re-run on 2026-10-04 (UTC); the Endpoint 1 response is from 2026-10-01. For a CSV batch converter and what invalid IDs return, see the Instagram ID to username guide.
Key takeaway (TL;DR) — Username → ID uses
instagram/v3/user-id-by-username(natgeo→787132in a 2026-10-01 test), and ID → username usesinstagram/v3/user-id-to-username, which returns the currentusernameat the top level of its data (787132→natgeoon 2026-10-04). Both arePOST https://api.sandbase.ai/v1/api/<model>calls with one SandBase API key, both were listed Free on 2026-10-04, and the data is read fromoutputs[0].data. For an ID that v3 can’t resolve,instagram/v1/user-info-by-id-v2says “This account does not exist.” instead of returning a 503.
Instagram User ID API: Username Lookup Guide
If you’ve ever tried to build Instagram automation (a follower tracker, an analytics dashboard, or a social monitoring agent), you’ve probably hit the same wall: many Instagram data workflows key accounts by a numeric user ID, not the username. That means before you can do anything useful, you need a reliable way to convert between usernames and IDs.
In this tutorial, I’ll show you how to do exactly that with an Instagram user ID API workflow built on two endpoints from the SandBase Instagram catalog (81 endpoints listed as of 2026-10-01). We’ll cover both directions (username → ID and ID → username), walk through real code examples, and discuss practical use cases for AI agents and automation workflows.
The catalog page is where that count comes from: it reads “81 SHOWN”, and the first card is the user-id-by-username endpoint this guide uses, marked Available · Free.

Caption: The SandBase Instagram catalog lists 81 endpoints; the selected v3/user-id-by-username card shows Available and Free (captured 2026-10-02).
Why You Need Instagram User ID Lookup
Most Instagram automation tasks require a numeric user ID rather than the human-readable username. Here’s why this matters:
- Follower tracking: To monitor follower changes over time, you need the target account’s user ID to query follower lists consistently.
- Content scheduling: Automation tools reference accounts by ID internally, even if users input a username.
- Analytics dashboards: Aggregating metrics across accounts requires stable identifiers — usernames can change, but IDs don’t.
- Cross-platform matching: When building unified social profiles (Instagram + TikTok + YouTube), user IDs provide the stable anchor for each platform.
- AI agent workflows: Agents that monitor competitors or track influencers need programmatic ID resolution as a first step.
Meta’s official route is Business Discovery: an authorized app user with a professional Instagram account and an access token can query business_discovery.username(...) and get another business or creator account’s ID. It doesn’t cover personal accounts and needs that app and authorization setup. SandBase is the lighter option when you only need public profile data.
| SandBase (this guide) | Meta Business Discovery | |
|---|---|---|
| Authentication | One SandBase API key | Meta app, professional Instagram account, access token with permissions |
| Target accounts | Public accounts returned by the endpoint | Business and creator accounts |
| Data / actions | Read-only public profile fields | Read fields of the target account within your authorized app |
| Limitations | Third-party endpoints; upstream errors happen (see below) | Meta app review, permissions, and rate limits |
Scope: public, read-only data
These endpoints return public, read-only Instagram account data. They need a SandBase API key, and SandBase is not an official Meta or Instagram partner. There is no access to private accounts, DMs, owner-only analytics, publishing, or account actions.
What SandBase Provides
SandBase offers a unified API layer for social media data, covering Instagram, TikTok, Douyin, YouTube, and more. For Instagram user ID lookups, this guide uses two endpoints:
| Direction | Endpoint | Description |
|---|---|---|
| Username → ID | instagram/v3/user-id-by-username | Get the numeric user ID for a username |
| ID → Username | instagram/v3/user-id-to-username | Get the current username (plus basic profile fields) for a numeric user ID |
Both are called the same way and come back in the same SandBase envelope (id, status, model, outputs[0].data). Two related endpoints come up below: instagram/v1/user-info-by-id-v2 is the fallback for IDs that v3 can’t resolve, and instagram/v1/user-info-by-id returns a much larger profile payload if you need fields that v3 doesn’t include.

Caption: An earlier capture of the SandBase model page for instagram/v3/user-id-by-username, whose playground takes a single required username input; the site layout has changed since, so the 2026-10-02 reference captures below show the current pages.
Getting Started
Prerequisites
- A SandBase account (sign up at sandbase.ai)
- An API key from your dashboard, exported as
SANDBASE_API_KEY - curl or Python (3.10+) installed
Authentication and calling convention
The examples use the Model API route: POST https://api.sandbase.ai/v1/api/<model> with a Bearer token and the parameters as a JSON body. This is the endpoint-reference surface, not the GET route shown on catalog pages; the reference’s generated samples may also include a model field, so treat the reference page as the source of truth.
Authorization: Bearer $SANDBASE_API_KEY
Content-Type: application/json
The reference documents the data under outputs[0].data; the Python helper below reads only that and raises an error when a call doesn’t complete. Open the user-id-by-username reference and the user-id-to-username reference for the request schemas.
The user-id-by-username reference shows the exact route and the single required username field used in the curl example below.

Caption: The instagram/v3/user-id-by-username reference documents POST /v1/api/instagram/v3/user-id-by-username, a required string username, and the id / status / model / outputs envelope (captured 2026-10-02).
Endpoint 1: Username to User ID
This is the most common use case. You have an Instagram username (like natgeo or nike) and need the corresponding numeric user ID.
curl Example
curl -X POST "https://api.sandbase.ai/v1/api/instagram/v3/user-id-by-username" \
-H "Authorization: Bearer $SANDBASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"username": "natgeo"}'
Response
Tested on 2026-10-01 (UTC) with the public National Geographic account (natgeo). The curl request above returned HTTP 200 and this complete response (run id 06c129cd-2779-4b5c-b409-b5137d2bd1d7):
{
"id": "06c129cd-2779-4b5c-b409-b5137d2bd1d7",
"status": "completed",
"model": "instagram/v3/user-id-by-username",
"outputs": [
{ "data": { "user_id": "787132" } }
]
}
The user_id field name is what I observed, not a documented guarantee. This endpoint returns the ID only; for profile metadata such as follower counts, pass the ID to Endpoint 2.
Python Example
import os
import requests
API_KEY = os.environ["SANDBASE_API_KEY"]
BASE_URL = "https://api.sandbase.ai/v1/api"
def call_model(model: str, params: dict) -> dict:
"""POST to a SandBase model and return outputs[0].data."""
response = requests.post(
f"{BASE_URL}/{model}",
headers={"Authorization": f"Bearer {API_KEY}"},
json=params,
timeout=120,
)
response.raise_for_status()
body = response.json()
outputs = body.get("outputs") or []
if body.get("status") != "completed" or not outputs:
raise RuntimeError(f"SandBase call did not complete: {body.get('status')} {body.get('error')}")
return outputs[0].get("data") or {}
def get_user_id_by_username(username: str) -> str | None:
"""Convert an Instagram username to a numeric user ID."""
data = call_model("instagram/v3/user-id-by-username", {"username": username})
return data.get("user_id")
# Usage
print(f"User ID: {get_user_id_by_username('natgeo')}")
Endpoint 2: User ID to Username
The reverse lookup is useful when you have stored user IDs (from webhooks, databases, or other API responses) and need to resolve them back to human-readable usernames. instagram/v3/user-id-to-username takes one required user_id and returns a compact profile with the current username at the top level of outputs[0].data. Send the ID as a string: on 2026-10-04, {"user_id": 787132} (a JSON number) returned HTTP 400 with parameter validation failed: /user_id: expected string, but got number.
curl Example
curl -X POST "https://api.sandbase.ai/v1/api/instagram/v3/user-id-to-username" \
-H "Authorization: Bearer $SANDBASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"user_id": "787132"}'
Response
Tested on 2026-10-04 (UTC) with natgeo’s ID 787132 from Endpoint 1. The curl request above returned this completed response in 2.78 seconds (run id 64abd76a-c4fe-4a0a-a3bf-7940cc058ba1). I omitted three keys from data (profile_pic_url, profile_pic_url_hd and an internal _rid); everything else is as returned:
{
"id": "64abd76a-c4fe-4a0a-a3bf-7940cc058ba1",
"status": "completed",
"model": "instagram/v3/user-id-to-username",
"outputs": [
{
"data": {
"biography": "Step into wonder and find your inner explorer with National Geographic 🌎",
"category": "",
"edge_follow": {"count": 194},
"edge_followed_by": {"count": 268458203},
"edge_owner_to_timeline_media": {"count": 32041},
"external_url": "http://visitstore.bio/natgeo",
"follower_count": 268458203,
"following_count": 194,
"full_name": "National Geographic",
"id": "787132",
"is_private": false,
"is_verified": true,
"media_count": 32041,
"pk": "787132",
"username": "natgeo"
}
}
]
}
These field names are what I observed, not a documented contract. username is the current handle, id and pk both echo the ID you sent, and the follower count appears twice, as follower_count and inside edge_followed_by.
Python Example
This reuses call_model from Endpoint 1. get_profile_by_id reads the username and the fields the follower tracker below needs; confirm_missing is the fallback check for an ID that v3 rejects.
def get_profile_by_id(user_id: str) -> dict:
"""Resolve a numeric user ID to its current username and basic profile fields."""
data = call_model("instagram/v3/user-id-to-username", {"user_id": str(user_id).strip()})
return {
"user_id": data.get("id", user_id),
"username": data.get("username"),
"full_name": data.get("full_name"),
"is_verified": data.get("is_verified"),
"follower_count": data.get("follower_count"),
}
def confirm_missing(user_id: str) -> str | None:
"""Ask instagram/v1/user-info-by-id-v2, which names a missing account instead of returning 503."""
data = call_model("instagram/v1/user-info-by-id-v2", {"user_id": str(user_id).strip()})
return data.get("errorMessage")
# Usage
profile = get_profile_by_id("787132")
print(f"Username: {profile['username']} ({profile['follower_count']:,} followers)")
unknown = "99999999999999999"
try:
get_profile_by_id(unknown)
except requests.HTTPError as e:
print(f"v3 returned HTTP {e.response.status_code}; fallback says: {confirm_missing(unknown)}")
Run on 2026-10-04 (UTC) together with the Endpoint 1 block, which supplies call_model and prints its own line first. The v3 lookup for 787132 took 3.68 seconds (run id 54f8b792-d29d-40c2-86fd-f8e614b84b32), the unknown ID got HTTP 503 from v3 after 3.15 seconds with no run id, and the fallback answered in 2.07 seconds (run id c7ce7248-1539-45c9-bcb5-73a343369d33):
User ID: 787132
Username: natgeo (268,458,201 followers)
v3 returned HTTP 503; fallback says: This account does not exist.
Unknown IDs and the fallback endpoints
instagram/v3/user-id-to-username doesn’t tell you when an ID is wrong. In the Instagram ID to username tests on 2026-10-04, an unknown, space-padded, non-numeric or empty ID all came back as HTTP 503 with the same generic upstream message, the same status a temporary upstream failure returns. Strip whitespace, check that the ID is all digits, and confirm a repeated 503 with instagram/v1/user-info-by-id-v2. For the unknown ID in the Python example above, it returned HTTP 200 and a completed run whose data names the problem; this is the complete response (run id c7ce7248-1539-45c9-bcb5-73a343369d33):
{
"id": "c7ce7248-1539-45c9-bcb5-73a343369d33",
"status": "completed",
"model": "instagram/v1/user-info-by-id-v2",
"outputs": [
{
"data": {
"errorMessage": "This account does not exist.",
"status": false
}
}
]
}
instagram/v1/user-info-by-id is the endpoint earlier versions of this post used for this step. It still works and takes the same one-field body, and it returns a much larger profile payload, so use it only if you need profile fields that v3 doesn’t return. Its reference lists user_id as the only required field:

Caption: The instagram/v1/user-info-by-id reference documents POST /v1/api/instagram/v1/user-info-by-id with a required string user_id; since 2026-10-04 this post uses it only as the larger-profile alternative to v3/user-id-to-username (captured 2026-10-02).
Practical Use Case: Building a Follower Tracking Agent
Let’s put this together in a more realistic scenario. Say you’re building an AI agent that collects follower counts for a list of competitor accounts. Each account takes two calls: username → ID with instagram/v3/user-id-by-username, then ID → profile with instagram/v3/user-id-to-username, which returns follower_count next to the current username. detect_changes compares a run against a previous snapshot you store yourself; loading that snapshot and sending alerts are left to your application.
import os
import json
from datetime import datetime, timezone
import requests
API_KEY = os.environ["SANDBASE_API_KEY"]
BASE_URL = "https://api.sandbase.ai/v1/api"
TRACKED_ACCOUNTS = ["nike", "adidas", "puma", "newbalance"]
def call_model(model: str, params: dict) -> dict:
"""POST to a SandBase model and return outputs[0].data."""
response = requests.post(
f"{BASE_URL}/{model}",
headers={"Authorization": f"Bearer {API_KEY}"},
json=params,
timeout=120,
)
response.raise_for_status()
body = response.json()
outputs = body.get("outputs") or []
if body.get("status") != "completed" or not outputs:
raise RuntimeError(f"SandBase call did not complete: {body.get('status')} {body.get('error')}")
return outputs[0].get("data") or {}
def get_user_id_by_username(username: str) -> str | None:
data = call_model("instagram/v3/user-id-by-username", {"username": username})
return data.get("user_id")
def get_profile_by_id(user_id: str) -> dict:
data = call_model("instagram/v3/user-id-to-username", {"user_id": str(user_id).strip()})
return {
"user_id": data.get("id", user_id),
"username": data.get("username"),
"full_name": data.get("full_name"),
"is_verified": data.get("is_verified"),
"follower_count": data.get("follower_count"),
}
def collect_follower_data(usernames: list) -> list:
"""Collect follower counts for a list of usernames."""
results = []
for username in usernames:
try:
user_id = get_user_id_by_username(username)
if not user_id:
print(f"No user ID returned for {username}")
continue
profile = get_profile_by_id(user_id)
results.append({
"username": profile["username"] or username,
"user_id": user_id,
"follower_count": profile["follower_count"],
"timestamp": datetime.now(timezone.utc).isoformat(),
})
except (requests.RequestException, RuntimeError) as e:
print(f"Error fetching {username}: {e}")
return results
def detect_changes(current: list, previous: list, threshold: float = 0.01) -> list:
"""Detect significant follower count changes (default: 1% threshold)."""
alerts = []
prev_map = {item["username"]: item["follower_count"] for item in previous}
for item in current:
username = item["username"]
old_count = prev_map.get(username)
new_count = item["follower_count"]
if not old_count or new_count is None:
continue
change_pct = (new_count - old_count) / old_count
if abs(change_pct) >= threshold:
alerts.append({
"username": username,
"old_count": old_count,
"new_count": new_count,
"change_pct": round(change_pct * 100, 2),
})
return alerts
# Run the tracker
if __name__ == "__main__":
current_data = collect_follower_data(TRACKED_ACCOUNTS)
# In production, load previous data from your database
# previous_data = load_from_db()
# alerts = detect_changes(current_data, previous_data)
print(json.dumps(current_data, indent=2))
Run on 2026-10-04 (UTC), the tracker made 8 calls in 14.4 seconds; each v3 ID → profile call took 2.6 to 2.8 seconds. This is its complete output:
[
{
"username": "nike",
"user_id": "13460080",
"follower_count": 291055136,
"timestamp": "2026-10-04T06:22:47.132230+00:00"
},
{
"username": "adidas",
"user_id": "20269764",
"follower_count": 29735709,
"timestamp": "2026-10-04T06:22:50.711929+00:00"
},
{
"username": "puma",
"user_id": "10145706",
"follower_count": 12939656,
"timestamp": "2026-10-04T06:22:54.329570+00:00"
},
{
"username": "newbalance",
"user_id": "204654945",
"follower_count": 9343844,
"timestamp": "2026-10-04T06:22:57.829458+00:00"
}
]
This pattern works as a scheduled task or as part of a larger social monitoring agent built with the OpenAI SDK.
Batch Processing: Multiple Usernames
When resolving many usernames at once, add basic rate limiting and error handling. This reuses get_user_id_by_username and get_profile_by_id from above:
import time
def batch_resolve_usernames(usernames: list, delay: float = 0.5) -> dict:
"""Resolve a batch of usernames to user IDs with rate limiting."""
results = {}
for username in usernames:
try:
user_id = get_user_id_by_username(username)
if not user_id:
results[username] = {"error": "no user_id returned"}
continue
profile = get_profile_by_id(user_id)
results[username] = {
"user_id": user_id,
"full_name": profile["full_name"],
"follower_count": profile["follower_count"],
}
except (requests.RequestException, RuntimeError) as e:
results[username] = {"error": str(e)}
time.sleep(delay) # Respect rate limits
return results
# Resolve 10 accounts
accounts = ["nike", "adidas", "puma", "newbalance", "underarmour",
"reebok", "asics", "fila", "converse", "vans"]
resolved = batch_resolve_usernames(accounts)
for username, info in resolved.items():
if "error" not in info:
count = info["follower_count"]
shown = f"{count:,}" if isinstance(count, int) else "n/a"
print(f"@{username} → ID: {info['user_id']} ({shown} followers)")
I ran this on 2026-10-04 (UTC) after the Endpoint 1 and Endpoint 2 blocks, which print their own lines first. These are the lines the batch loop printed:
@nike → ID: 13460080 (291,055,142 followers)
@adidas → ID: 20269764 (29,735,711 followers)
@puma → ID: 10145706 (12,939,656 followers)
@newbalance → ID: 204654945 (9,343,841 followers)
@underarmour → ID: 5858873 (8,138,094 followers)
@reebok → ID: 186499819 (4,367,339 followers)
@asics → ID: 242236354 (1,508,212 followers)
@converse → ID: 6716197 (10,245,907 followers)
@vans → ID: 18919774 (15,655,811 followers)
Nine of the 10 accounts resolved, and each v3 ID → profile call took 2.6 to 4.4 seconds. fila is missing because instagram/v3/user-id-by-username returned a completed run with an empty ID ({"user_id": ""}, run id 76a0bdf8-edee-4985-bdfc-e701a6b1d71c), so the if not user_id branch stored it as an error; I didn’t dig into why. My first attempt at this run stopped on a connection reset from my local network, which an except (requests.HTTPError, RuntimeError) clause doesn’t catch, so both loops in this post now catch requests.RequestException, which covers HTTP and connection errors.
Combining with Other Social APIs
One of SandBase’s strengths is its unified approach to social media data. You can combine Instagram user ID lookups with APIs for other platforms to build cross-platform profiles.
For example, if you’re building a multi-platform influencer database, you might:
- Resolve Instagram username → user ID via
instagram/v3/user-id-by-username - Query Douyin user search to find the same creator on the Chinese platform
- Pull TikTok profile data for the global short-video presence
- Merge all data into a unified profile with stable IDs from each platform
This cross-platform approach is especially useful for AI agents working with social media data, where the agent needs a consistent identity layer across multiple data sources. For the Douyin side, see the Douyin user search API tutorial.
Error Handling Best Practices
The handling below is generic application guidance, not an endpoint-specific error contract. The notes after it say which statuses I actually saw.
import time
def safe_lookup(username: str, retried: bool = False) -> str | None:
"""Username lookup with basic HTTP error handling."""
try:
return get_user_id_by_username(username)
except requests.HTTPError as e:
status = e.response.status_code
if status == 401:
print("Invalid API key — check your credentials")
elif status == 429 and not retried:
print("Rate limit hit — back off and retry once")
time.sleep(60)
return safe_lookup(username, retried=True)
elif status in (500, 502, 503) and not retried:
print(f"Upstream/server error {status} — retrying once")
time.sleep(5)
return safe_lookup(username, retried=True)
elif status == 503:
print(f"Upstream error again for @{username} — the account may not exist, or the upstream is failing")
else:
print(f"Unexpected error: {e}")
except RuntimeError as e:
print(e)
return None
What I observed on 2026-10-01 (UTC) with instagram/v3/user-id-by-username:
- 401: an invalid API key returned HTTP 401 with
{"error": "invalid API key"}. - 503: upstream failures came back as HTTP 503 with an
errormessage and no run id. This happened intermittently for real accounts (a few calls out of a 14-account run), and a retry usually succeeded. A non-existent username also returned HTTP 503, not 404, so a repeated 503 for one username can mean the account doesn’t exist. - 429: I didn’t hit a rate limit; the backoff branch follows generic practice.
instagram/v3/user-id-to-username behaved the same way on 2026-10-04 (UTC): both calls for the unknown ID 99999999999999999 returned HTTP 503 with no run id and the message upstream error 400: Request failed. Please retry., plus a note that the request wasn’t charged. Endpoint 2 shows how to confirm a missing account with instagram/v1/user-info-by-id-v2. I didn’t hit a rate limit in the more than 70 Instagram calls of this refresh either.
Tips for Production Use
- Cache aggressively: User IDs don’t change. Once you’ve resolved a username → ID mapping, store it locally.
- Handle username changes: Usernames can change, but IDs are permanent. Store the ID as your primary key.
- Batch during off-peak: If resolving hundreds of accounts, run batch jobs during low-traffic hours.
- Monitor your usage: Check credit usage in your SandBase dashboard.
- Schedule it yourself: The two endpoints here are plain read calls. Scheduling, webhooks, and alerting are patterns you build around them in your own application.
Summary
Converting between Instagram usernames and user IDs is a foundational step for almost any Instagram automation workflow. SandBase handles it with two endpoints:
instagram/v3/user-id-by-username— resolve username to numeric IDinstagram/v3/user-id-to-username— resolve ID back to the current username + basic profile info
For an ID that v3 answers with repeated 503s, instagram/v1/user-info-by-id-v2 tells you whether the account exists. For batch conversion of stored IDs and the full list of edge cases, see the Instagram ID to username guide.
Whether you’re building a follower tracking agent, a social media dashboard, or a cross-platform influencer database, these endpoints give you the stable identifiers you need to build reliable automation.
Ready to get started? Sign up for a SandBase API key, open the user-id-by-username and user-id-to-username API references, and try the examples above. For more advanced workflows, check out our guide on building a social monitoring agent with the OpenAI SDK.