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".

GET/api/auth/oauth2/userinfo

POST works too, with the same header and the same response.

Authorizations

Authorizationheaderstringrequired
Bearer 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.

ClaimScopeExampleWhat it is
subalwaysclz9k2x0a0000s601f8h3d7q2The stable haunt.gg user id. It never changes — key your own user records on this, not on the username.
uididentify1337The short numeric id shown on the profile.
nameidentifyJohn DoeThe name shown on the profile. Free text — not unique and not a handle.
preferred_usernameidentifyjohnThe handle in the profile URL. Always lowercase, unique, and the user can change it.
pictureidentifyhttps://r2.haunt.gg/avatar/abc123.pngThe avatar of the user's active profile.
profileidentifyhttps://haunt.gg/johnThe user's public page.
emailemail[email protected]The address the user signs in with.
email_verifiedemailtrueWhether they confirmed it.
connectionsconnectionssee belowArray 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

Request
curl "https://haunt.gg/api/auth/oauth2/userinfo" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response

Response
Every scope granted. With openid identify you get everything down to profile; email and connections appear solely with their own scope.
{
  "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.

Response
Decoded payload for a token granted openid identify email. auth_time is when the user actually signed in; the token itself is valid for 10 hours.
{
  "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.