Overview
YouSeemLegit is an OAuth 2.0 authorization server with OpenID Connect on top, plus a hosted login page you brand. You register a client, send visitors to the authorize endpoint, and receive an authorization code at a redirect URI you control. Everything after that is three JSON endpoints over HTTPS.
Key conventions differ by endpoint, because the OAuth endpoints follow the spec and the legacy ones keep compatibility. /api/oauth/* is snake_case only. /api/auth/token/refresh accepts and returns both. /api/auth/token/verify takes the key token.
The flow
- Redirect the visitor to
/api/oauth/authorizewithresponse_type=code, yourclient_id,redirect_uriand astateyou generate. - They authenticate on the branded login page — password, Google, LinkedIn, magic link, and 2FA if enabled.
- We redirect to your
redirect_uriwithcodeand yourstate, unchanged. - Verify the state, then
POST /api/oauth/tokenwithgrant_type=authorization_code. - Use the access token. Read the profile from
/api/oauth/userinfo, and rotate withgrant_type=refresh_tokenbefore it expires.
Authorization codes are single-use. Redeeming one twice returns invalid_grant: code already used and is logged as a reuse event.
Token
/api/oauth/token— Both grants live hereAuthenticate with HTTP Basic or by putting client_id and client_secret in the body. Public clients using PKCE may omit the secret.
grant_type=authorization_code
grant_type=refresh_token
Refresh is a rotation: the token you send is replaced, and the response contains its successor. Persist the new one before you respond to your user, or a crash in between signs them out.
The id_token is signed with the same key as the access token. If you need to validate identity independently, call /api/oauth/userinfo rather than treating the two tokens as separately trusted.
UserInfo
/api/oauth/userinfo— Standard OIDC claimsThis is where names and avatars come from. Pass the access token as a bearer token; both GET and POST are accepted.
Key your records on sub. A member can change their email address, and identity linking means one person may arrive through more than one provider.
Verify
/api/auth/token/verify— Cheap session checkConfirms an access token is valid and its session is live, and tells you how long is left. Useful as a per-request guard when you do not need profile fields.
Two things that catch people out: the request key is token, not access_token; and an invalid token returns 200 with valid: false, not a 401. Branch on the valid field.
shouldRefresh turns true when under five minutes remain, so you can rotate opportunistically instead of tracking expiry yourself.
Legacy refresh
/api/auth/token/refresh— Dual key conventionAn alternative to grant_type=refresh_token that accepts and returns both camelCase and snake_case. It exists for integrations that predate the OAuth endpoints. New code should prefer /api/oauth/token; this one is supported and not going away.
It does not require client credentials — possession of the refresh token is sufficient. Treat refresh tokens as secrets and keep them server-side.
Bulk import
/api/admin/users/import— Server-to-server, x-api-keyMove an existing membership over in batches of up to 1,000. Existing emails are skipped rather than duplicated, and a batch with some bad rows returns 207 with per-row reasons instead of failing wholesale. Image URLs must be HTTPS.
Imported users are not asked to reset anything. Members who used a social provider keep using it; members with passwords keep theirs.
Errors
The OAuth endpoints return { error, error_description } with Cache-Control: no-store. Codes are stable; descriptions are for humans and may change — branch on the code.
/api/auth/token/verify is the exception: it answers 200 with valid: false instead of an error status.
Rate limits
Limits protect accounts from grinding, not your application from working. Verification is deliberately unlimited so you can check a token on every request.
Repeated failures against one account trigger a progressive lockout, which is reported separately from a rate limit. Back off with exponential delay and surface a wait message rather than looping.
Something missing here?
Tell us what you were looking for and we will write it.