Lookup User
Look up a user by username, uid, id, or Discord id — optionally with full profile data.
Resolve a haunt.gg user by their username, numeric uid, internal id, or
linked discord account id. By default the response is a lean basic payload; opt
into additional data (full profile, badges, links, projects, and more) with
per-section include flags so you only ever transfer what you need.
/api/lookup/userUsers can switch off public API access for their profile in their account
security settings. Lookups for those users answer 404 with
{"error":"This profile is private."} no matter which type you resolve
them by — treat it as "exists, but not available here" rather than retrying
with another identifier.
Authorizations
X-API-Keyheaderstringrequiredlookup:user permission (granted by staff). Treat it like a password.Query Parameters
typequerystringrequiredvalue. One of username, uid, id, or discord. Username matching is case-insensitive and falls back to aliases; discord resolves the user via their linked Discord account.valuequerystringrequiredtype. 1–64 characters.showAliasProfilequerybooleanvalue resolves through an alias that points to a custom profile, scope all profile-derived data — profile, badges, links, widgets, audios, backgrounds, projects, gallery, sections, and the user's avatarUrl — to that alias profile instead of the user's active one. Only views, aliases, feedback and comments stay user-wide. No-op for direct username/uid/id/discord matches and for aliases without a custom profile. Pass true. Defaults to false.profilequerybooleansettings object. Pass the literal string true. Defaults to false.badgesquerybooleanenabled flag plus its per-profile display overlay (color, recolor, selected tier, order). Filter on enabled client-side if you only want the visible ones. Catalog badges carry ready-to-embed artwork as imageUrl (plus one per entry in tiers); custom badges carry their upload under image or, when the user picked an icon instead, server-resolved markup under iconSvg. The profile-wide badge styling (monochrome, glow, border, scroll, …) lives separately under profile.settings.badgesSettings (request profile=true). Pass true. Defaults to false.widgetsquerybooleantrue. Defaults to false.audiosquerybooleantrue. Defaults to false.backgroundsquerybooleantrue. Defaults to false.linksquerybooleanicon or server-resolved Iconify markup in iconSvg — never both. Pass true. Defaults to false.projectsquerybooleantrue. Defaults to false.viewsquerybooleanshowAliasProfile does not change it. Pass true. Defaults to false.aliasesquerybooleantrue. Defaults to false.galleryquerybooleanhalf/full/quad, and resolved asset URL), in section-then-item order. Pass true. Defaults to false.sectionsquerybooleancontent, skills and widgetIds; Projects sections carry projectIds — resolve those id references against the widgets/projects includes. The four catalog sections carry their imported items inline. Pass true. Defaults to false.feedbackquerybooleanlikes and dislikes counts). Votes from banned users are excluded. Pass true. Defaults to false.commentsquerybooleancomments.items (newest first, pinned bubble up; capped at 100, replies not expanded — use replyCount), alongside comments.count. Both exclude replies and comments written by banned users, and count is the full total, so it exceeds items.length once a profile passes 100 comments. Pass true. Defaults to false.Only the sections you opt into are queried and serialized, so the default
payload stays small. Include flags are read as exact strings — pass true
(e.g. profile=true); any other value is treated as false. The same exact-string
rule applies to showAliasProfile.
Discord lookups — resolve a Discord user straight to their haunt.gg account
with their snowflake id: ?type=discord&value=1363890885443452989. Returns
404 when no haunt.gg user has that Discord account linked.
Alias profiles — when a user runs multiple aliases with their own custom
profiles, add showAliasProfile=true to a username/alias lookup to scope the
returned profile, badges, links, widgets, audios, backgrounds, projects,
gallery, sections and avatarUrl to the alias's profile rather than the active
one — e.g. ?type=username&value=john-music&profile=true&showAliasProfile=true.
Example Request
curl -X GET "https://haunt.gg/api/lookup/user?type=username&value=john&profile=true" \
-H "X-API-Key: YOUR_API_KEY"Asset URLs
Every uploaded asset — avatars, banners, cursors, backgrounds, audio and
covers, link and project icons, gallery images, fonts, sounds and custom badge
art — resolves to https://r2.haunt.gg/…. The only exception is catalog badge
artwork (badges[].imageUrl and badges[].tiers[].imageUrl), which is served
from https://assets.haunt.gg/badges/haunt/….
One more shape to expect: when a profile is set to mirror its Discord avatar,
user.avatarUrl and profile.avatar point at
https://haunt.gg/api/discord/avatar/<userId> instead of an uploaded file.
Response
{
"error": null,
"user": {
"id": "clz9k2x0a0000s601f8h3d7q2",
"uid": 1337,
"username": "john",
"name": "John",
"avatarUrl": "https://r2.haunt.gg/avatar/abc123.png",
"discordId": "1363890885443452989",
"lastfmUsername": "john",
"premium": true,
"verified": false,
"imagehost": false,
"inactive": false,
"createdAt": "2026-01-15T10:30:00.000Z"
},
"profile": {
"profileId": "clz9k3p1b0001s601a2b4c5d6",
"name": "john",
"displayName": "John",
"description": "just vibing",
"location": "Berlin",
"theme": "default",
"font": "inter",
"fontUsername": "inter",
"soundEffect": "none",
"soundEffectButton": "none",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-05-09T12:34:56.000Z",
"avatar": {
"url": "https://r2.haunt.gg/avatar/abc123.png",
"mime": "image/png",
"width": 512,
"height": 512,
"duration": null
},
"banner": null,
"cursor": null,
"cursorPointer": null,
"fontAsset": null,
"fontUsernameAsset": null,
"soundEffectAsset": null,
"soundEffectButtonAsset": null,
"metadataFavicon": null,
"metadataBanner": null,
"settings": { "…": "22 keys — see the profile.settings tab" }
},
"badges": [
{
"id": "clz9kb1a0001s601donator00",
"type": "STANDARD",
"badgeId": "donator",
"enabled": true,
"selectedTier": 2,
"unlockedTiers": [
1,
2
],
"color": null,
"recolor": false,
"name": null,
"order": 0,
"image": null,
"imageUrl": "https://assets.haunt.gg/badges/haunt/donator_fortune.png",
"iconSvg": null,
"tiers": [
{
"tier": 1,
"threshold": 10,
"title": "Donator",
"description": "Donate at least 10 EUR to haunt.gg.",
"color": "#13a15a",
"unlocked": true,
"selected": false,
"imageUrl": "https://assets.haunt.gg/badges/haunt/donator.png"
},
{
"tier": 2,
"threshold": 50,
"title": "Fortune",
"description": "Donate at least 50 EUR to haunt.gg.",
"color": "#aa3b3b",
"unlocked": true,
"selected": true,
"imageUrl": "https://assets.haunt.gg/badges/haunt/donator_fortune.png"
}
]
},
{
"id": "clz9kc2b0002s601custom000",
"type": "CUSTOM",
"badgeId": null,
"enabled": false,
"selectedTier": null,
"unlockedTiers": [],
"color": "#ff66cc",
"recolor": true,
"name": "Early Bird",
"order": 1,
"image": {
"url": "https://r2.haunt.gg/badge/earlybird.png",
"mime": "image/png",
"width": 64,
"height": 64,
"duration": null
},
"imageUrl": null,
"iconSvg": null,
"tiers": []
}
],
"widgets": [
{
"id": "clz9kw1c0001s601widget001",
"platform": "spotify",
"type": "track",
"identifier": "3n3Ppam7vgaVa1iaRUc9Lp",
"order": 0
}
],
"audios": [
{
"id": "clz9ka1d0001s601audio0001",
"title": "Midnight",
"artist": "John",
"externalUrl": "https://open.spotify.com/track/3n3Ppam7vgaVa1iaRUc9Lp",
"order": 0,
"audio": {
"url": "https://r2.haunt.gg/audio/midnight.mp3",
"mime": "audio/mpeg",
"width": null,
"height": null,
"duration": 213
},
"cover": {
"url": "https://r2.haunt.gg/audio/cover/midnight.png",
"mime": "image/png",
"width": 640,
"height": 640,
"duration": null
}
}
],
"backgrounds": [
{
"id": "clz9kbg1e0001s601bg000001",
"title": "Nebula",
"order": 0,
"asset": {
"url": "https://r2.haunt.gg/background/nebula.jpg",
"mime": "image/jpeg",
"width": 1920,
"height": 1080,
"duration": null
}
}
],
"links": [
{
"id": "clz9kl1a0001s601link00001",
"title": "GitHub",
"url": "https://github.com/john",
"platformId": "github",
"platformType": "social",
"isText": false,
"color": null,
"recolor": false,
"order": 0,
"icon": null,
"iconSvg": null
},
{
"id": "clz9kl2b0002s601link00002",
"title": "My Portfolio",
"url": "https://john.dev",
"platformId": null,
"platformType": null,
"isText": false,
"color": "#7c3aed",
"recolor": true,
"order": 1,
"icon": {
"url": "https://r2.haunt.gg/link/portfolio.png",
"mime": "image/png",
"width": 128,
"height": 128,
"duration": null
},
"iconSvg": null
}
],
"projects": [
{
"id": "clz9kj1e0004s601c7d8e9f0",
"title": "haunt.gg",
"description": "A bio-link platform.",
"tags": [
"nextjs",
"typescript"
],
"url": "https://haunt.gg",
"order": 0,
"icon": {
"url": "https://r2.haunt.gg/project/haunt.png",
"mime": "image/png",
"width": 256,
"height": 256,
"duration": null
}
}
],
"views": 18234,
"aliases": [
{
"name": "john-music",
"redirect": false,
"profileId": "clz9k3p1b0001s601a2b4c5d6"
},
{
"name": "j",
"redirect": true,
"profileId": null
}
],
"gallery": [
{
"id": "clz9kg1a0000s601m1n2o3p4",
"caption": "Sunset",
"width": "half",
"asset": {
"url": "https://r2.haunt.gg/gallery/abc.png",
"mime": "image/png",
"width": 1200,
"height": 800,
"duration": null
}
}
],
"sections": [
{
"id": "clz9ka1b0001s601q5r6s7t8",
"type": "ABOUT_ME",
"order": 0,
"title": "About Me",
"description": null,
"alignment": "left",
"enabled": true,
"content": "hey, I build things.",
"widgetIds": [
"clz9kw1c0001s601widget001"
],
"skills": {
"frontend": [
"react",
"typescript"
],
"backend": [
"node"
],
"database": [],
"core": [],
"game": [],
"tools": [],
"cloud": [],
"mobile": [],
"design": [],
"music": [],
"custom": [
{
"label": "Shipping fast",
"iconName": "solar:rocket-bold",
"iconSvg": "<svg …>"
}
]
},
"skillsEnabled": true,
"skillsAlignment": "left"
},
{
"id": "clz9kp1d0003s601y3z4a5b6",
"type": "PROJECTS",
"order": 1,
"title": null,
"description": null,
"alignment": "center",
"projectIds": [
"clz9kj1e0004s601c7d8e9f0"
]
},
{
"id": "clz9kt1h0007s601o9p0q1r2",
"type": "CUSTOM_TEXT",
"order": 2,
"title": "Notes",
"description": null,
"alignment": "left",
"enabled": true,
"content": "Anything I feel like writing."
},
{
"id": "clz9kq1f0005s601g1h2i3j4",
"type": "GALLERY",
"order": 3,
"title": "Gallery",
"description": null,
"alignment": "left",
"items": [
{
"id": "clz9kg1a0000s601m1n2o3p4",
"caption": "Sunset",
"width": "half",
"asset": {
"url": "https://r2.haunt.gg/gallery/abc.png",
"mime": "image/png",
"width": 1200,
"height": 800,
"duration": null
}
}
]
},
{
"id": "clz9kr1g0006s601k5l6m7n8",
"type": "AUDIO_PLAYER",
"order": 4,
"title": null,
"description": null,
"alignment": "left"
},
{
"id": "clz9ku1i0008s601s3t4u5v6",
"type": "GAMES",
"order": 5,
"title": "Games",
"description": null,
"alignment": "left",
"games": [
{
"rawgId": 3498,
"name": "Grand Theft Auto V",
"image": "https://media.rawg.io/media/games/456/456dea5e1c7e3cd07060c14e96612001.jpg",
"slug": "grand-theft-auto-v",
"released": "2013-09-17",
"rating": 4.47,
"metacritic": 92
}
]
},
{
"id": "clz9kv1j0009s601w7x8y9z0",
"type": "ANIME",
"order": 6,
"title": "Anime",
"description": null,
"alignment": "left",
"anime": [
{
"anilistId": 21,
"title": "ONE PIECE",
"image": "https://s4.anilist.co/file/anilistcdn/media/anime/cover/medium/bx21.jpg",
"format": "TV",
"year": 1999,
"score": 88
}
]
},
{
"id": "clz9kx1k0010s601a1b2c3d4",
"type": "MEDIA",
"order": 7,
"title": "Movies and Series",
"description": null,
"alignment": "left",
"media": [
{
"tmdbId": 1396,
"mediaType": "tv",
"title": "Breaking Bad",
"image": "https://image.tmdb.org/t/p/w342/ggFHVNu6YYI5L9pCfOacjizRGt.jpg",
"year": 2008,
"rating": 8.9
}
]
},
{
"id": "clz9ky1l0011s601e5f6g7h8",
"type": "MUSIC",
"order": 8,
"title": "Music",
"description": null,
"alignment": "left",
"music": [
{
"kind": "track",
"spotifyId": "3n3Ppam7vgaVa1iaRUc9Lp",
"title": "Mr. Brightside",
"artist": "The Killers",
"image": "https://i.scdn.co/image/ab67616d0000b273ccdddd46119a4ff53eaf1f5d",
"album": "Hot Fuss"
},
{
"kind": "album",
"spotifyId": "6TJmQnO44YE5BtTxH8pop1",
"title": "Hot Fuss",
"artist": "The Killers",
"image": "https://i.scdn.co/image/ab67616d0000b273ccdddd46119a4ff53eaf1f5d"
},
{
"kind": "artist",
"spotifyId": "0C0XlULifJtAgn6ZNCW2eu",
"title": "The Killers",
"image": "https://i.scdn.co/image/ab6761610000e5eb2c1c1c1c1c1c1c1c1c1c1c1c"
}
]
}
],
"feedback": {
"likes": 42,
"dislikes": 3
},
"comments": {
"count": 137,
"items": [
{
"id": "clz9k7c2d0002s601e9f0g1h2",
"content": "great page!",
"likeCount": 5,
"replyCount": 2,
"pinned": true,
"createdAt": "2026-05-09T12:34:56.000Z",
"author": {
"id": "clz9k8d3e0003s601i2j3k4l5",
"uid": 99,
"username": "jane",
"name": "Jane",
"avatarUrl": "https://r2.haunt.gg/avatar/jane.png"
}
}
]
}
}Rate limits
Two buckets apply at once:
| Bucket | Limit |
|---|---|
| Per API key | 30 requests/minute, or 60 if the key's owner has premium. |
| Per IP address | 60 requests/minute across all of /api/*. |
A free-tier key is therefore effectively capped at 30 requests per minute.
Things worth knowing
- The lookup payload is deliberately leaner than what the public profile page renders. Widgets come without their display options and images, audio without synced lyrics, backgrounds without their device targeting.
sections[].widgetIdsis a flat list of ids — the per-section widget widths are not exposed here.- A section whose stored configuration no longer validates is silently
dropped from
sections, and disabled sections are never returned at all. - Any
/api/*request that reaches the origin without Cloudflare'scf-connecting-ipheader is refused with403and{"error":"errors.forbidden"}before the route runs. This only affects callers that bypass Cloudflare.