Lookup Presence
Read the live Discord presence of a member of the haunt.gg server.
Read the live Discord presence of a member of the haunt.gg Discord server by their account snowflake: status, current activity, profile media (avatar, banner, decoration, nameplate), server nickname, bio, guild tag and Discord badges — the same payload that powers the Discord presence widget on a profile.
/api/lookup/presence/{discordId}Presence is only visible for members of the haunt.gg Discord
server — that is where the bot observes it. A
Discord account that exists but has not joined answers 404 with
{"miss":"notMember"}, so you can tell "not a member" apart from "no such
account" without a second request.
Authorizations
X-API-Keyheaderstringrequiredlookup:presence permission (granted by staff). Treat it like a password.Path Parameters
discordIdpathstringrequired400 before the lookup runs. This is a Discord id, not a haunt.gg uid: to go the other way, resolve the haunt.gg user first with GET /api/lookup/user?type=username&value=… and read user.discordId.Example Request
curl -X GET "https://haunt.gg/api/lookup/presence/1363890885443452989" \
-H "X-API-Key: YOUR_API_KEY"Status values
status.status is the member's effective status, collapsed across their
devices — the first non-offline of desktop, mobile and web. Streaming wins over
everything: a member who is live reads streaming no matter what their manual
status says.
| Value | Meaning |
|---|---|
online | Active on at least one device. |
idle | Away — set manually or by inactivity. |
dnd | Do Not Disturb. |
streaming | Has a Streaming activity running. |
offline | Offline, invisible, or has no presence the bot can see. |
status.url is a ready-to-embed icon on https://assets.haunt.gg/status/…,
one PNG per value above.
Activities
activity is the single activity worth showing, picked in this order: Spotify
first, then Playing / Streaming / Watching, then any other Listening activity,
then a Custom Status. activities carries every activity the member has
running, unfiltered and in Discord's own order — activity is always one of
them (or null when there are none).
namestringSpotify, or the app. Empty for a plain custom status with no text.typenumber0 Playing, 1 Streaming, 2 Listening, 3 Watching, 4 Custom Status, 5 Competing.statestring | nullby <artist> shaped text for music.detailsstring | nulltooltipstring | nullemojistring | nullnull when there is none.imagestring | nullnull when the activity ships none.Badges
badges is an object keyed by badge id, ordered the way the presence widget
renders it. Every entry carries a display name and a ready-to-embed url on
https://assets.haunt.gg/badges/discord/…. Only badges the account actually
holds are present, so iterate the object rather than indexing fixed keys.
Possible keys, in canonical order: nitro, staff, partner, moderator,
events, bravery, brilliance, balance, bughunter1, bughunter2,
developer, earlysupporter, boosted, legacy, quest, leaf, orb,
gifting.
Response
{
"id": "1363890885443452989",
"username": "john",
"globalName": "John",
"serverNick": "john the third",
"avatar": "https://cdn.discordapp.com/avatars/1363890885443452989/a1b2c3d4e5f6.png",
"serverAvatar": null,
"banner": "https://cdn.discordapp.com/banners/1363890885443452989/f6e5d4c3b2a1.png",
"serverBanner": null,
"decoration": "https://cdn.discordapp.com/avatar-decoration-presets/a_9f8e7d6c5b4a.png",
"nameplate": "https://cdn.discordapp.com/assets/collectibles/nameplates/twilight/asset.webm",
"biography": "just here for the music",
"activity": {
"name": "Spotify",
"type": 2,
"state": "by Burial",
"details": "Archangel",
"tooltip": "Untrue",
"emoji": null,
"image": "https://i.scdn.co/image/ab67616d0000b273a1b2c3d4e5f6"
},
"activities": [
{
"name": "Spotify",
"type": 2,
"state": "by Burial",
"details": "Archangel",
"tooltip": "Untrue",
"emoji": null,
"image": "https://i.scdn.co/image/ab67616d0000b273a1b2c3d4e5f6"
},
{
"name": "Custom Status",
"type": 4,
"state": "building things",
"details": null,
"tooltip": null,
"emoji": "https://cdn.discordapp.com/emojis/1234567890123456789.png",
"image": null
}
],
"badges": {
"nitro": {
"name": "Discord Nitro",
"url": "https://assets.haunt.gg/badges/discord/premium.png"
},
"boosted": {
"name": "Server Boosting since 12 Jan 2025",
"url": "https://assets.haunt.gg/badges/discord/guild_booster_lvl9.png"
}
},
"status": {
"status": "online",
"url": "https://assets.haunt.gg/status/online.png"
},
"last_online": 1756400000,
"clan": {
"tag": "HNT",
"badge": "https://cdn.discordapp.com/guild-tag-badges/1363890885443452989/a1b2c3d4.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
- Freshness. Status and activities are read live on every request. The
member record behind the names, avatars, banner and guild tag is cached for
30 seconds, and the Discord profile behind
biographyand the external badges (Nitro, Server Booster, Quest, …) for 24 hours — a fresh Nitro subscription can take up to a day to appear here. - Server profile vs. account profile.
serverNick,serverAvatarandserverBannerare the member's haunt.gg-server profile and arenullfor most members. Fall back toglobalName→usernamefor the name and toavatar/bannerfor the media, which is what the profile widget does. last_onlineis a Unix timestamp in seconds, recorded by the bot when the member last went offline. It isnullfor a member who has never been seen going offline.nameplateis a WebM video, not an image — render it in a muted, looping<video>rather than an<img>.clanis the member's Discord guild tag (the short badge next to their name),nullwhen they display none.- 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.