User info
Read the claims the user approved for your app.
The OpenID Connect userinfo endpoint. Call it with an access token and you get back exactly the claims the granted scopes cover — nothing is emitted "for convenience".
/api/auth/oauth2/userinfoPOST works too, with the same header and the same response.
Authorizations
AuthorizationheaderstringrequiredBearer YOUR_ACCESS_TOKEN. The token's granted scopes must include openid, otherwise the endpoint answers 400 invalid_scope no matter what else it carries.Claims
Every claim hangs off a scope. sub is the only one you always get.
| Claim | Scope | Example | What it is |
|---|---|---|---|
sub | always | clz9k2x0a0000s601f8h3d7q2 | The stable haunt.gg user id. It never changes — key your own user records on this, not on the username. |
uid | identify | 1337 | The short numeric id shown on the profile. |
name | identify | John Doe | The name shown on the profile. Free text — not unique and not a handle. |
preferred_username | identify | john | The handle in the profile URL. Always lowercase, unique, and the user can change it. |
picture | identify | https://r2.haunt.gg/avatar/abc123.png | The avatar of the user's active profile. |
profile | identify | https://haunt.gg/john | The user's public page. |
email | email | [email protected] | The address the user signs in with. |
email_verified | email | true | Whether they confirmed it. |
connections | connections | see below | Array of provider / handle pairs. Only discord and lastfm exist today. |
These are the OpenID Connect standard claim names, so any OIDC client library
fills in its user object on its own — there is nothing haunt-specific to map.
uid is the one claim the standard has no name for.
Example Request
curl "https://haunt.gg/api/auth/oauth2/userinfo" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Response
{
"sub": "clz9k2x0a0000s601f8h3d7q2",
"uid": 1337,
"name": "John Doe",
"preferred_username": "john",
"picture": "https://r2.haunt.gg/avatar/abc123.png",
"profile": "https://haunt.gg/john",
"email": "[email protected]",
"email_verified": true,
"connections": [
{ "provider": "discord", "handle": "1363890885443452989" },
{ "provider": "lastfm", "handle": "john" }
]
}Or just read the id_token
If all you need is identity, you do not have to call this endpoint at all — the
id_token from the token response already carries the identify and email
claims. It deliberately leaves out the bulkier connections, which only the
userinfo endpoint returns.
The token is a JWT signed with EdDSA (Ed25519). Verify it against the JWKS
at https://haunt.gg/api/auth/jwks, and check that iss is https://haunt.gg,
that aud is your client_id, and that nonce matches the one you sent.
{
"iss": "https://haunt.gg",
"sub": "clz9k2x0a0000s601f8h3d7q2",
"aud": "YOUR_CLIENT_ID",
"iat": 1767222000,
"exp": 1767258000,
"auth_time": 1767221900,
"acr": "urn:mace:incommon:iap:bronze",
"nonce": "YOUR_NONCE",
"uid": 1337,
"name": "John Doe",
"preferred_username": "john",
"picture": "https://r2.haunt.gg/avatar/abc123.png",
"profile": "https://haunt.gg/john",
"email": "[email protected]",
"email_verified": true
}Rate limit
100 requests per minute per IP address, counted for this endpoint alone.