Overview
Hosts, authentication, and the conventions every Client API route shares.
LoginWith is a standard OAuth 2.1 authorization server. Everything the SDKs do is an HTTP call you can make yourself. This tab covers what a client app, and the users signed in to it, call on the org’s issuer: discovery, the OAuth endpoints, the user’s profile and their tenants. Administering an org is the Management API.
Hosts
| Host | What it serves |
|---|---|
https://{slug}.loginwith.dev | Your issuer: discovery, JWKS, the OAuth endpoints, /users/me, /tenants and the management API. The host decides the org: every request is answered as the org whose slug it names. |
https://{slug}.loginwith.page | Your hosted login page (or your custom domain). /oauth/authorize sends the browser here; you never call it yourself. |
https://console.loginwith.dev | The console. Its issuer is https://loginwith.loginwith.dev/. |
The examples use the acme org: https://acme.loginwith.dev. Start from the
discovery document; a client
library finds every other endpoint from it.
Authentication
Every route needs Authorization: Bearer <access token> except:
| Public route | |
|---|---|
GET /.well-known/openid-configuration, GET /.well-known/oauth-authorization-server, GET /.well-known/jwks.json | Discovery and keys |
GET /oauth/authorize | Where sign-in starts |
POST /oauth/token, POST /oauth/revoke | The credential is in the body |
POST /oauth/register | Rate-limited |
GET /health | {"name", "version", "env"} |
GET /cdn/sdk-latest | The script-tag SDK |
…plus the hosted page’s internal endpoints. Unknown paths are behind
authentication too: without a token they answer 401, not 404.
The token must be a user token minted by the org whose host you call. Its shape and claims are in Tokens.
| Status | Body | WWW-Authenticate |
|---|---|---|
401 | {"error": "unauthorized", "error_detail": "we don't know who you are"}: no token | Bearer realm="acme.loginwith.dev" |
401 | {"error": "unauthorized", "error_detail": "your session expired"}, or "your session ended", or "we couldn't check who you are" | Bearer realm="…", error="invalid_token", error_description="…" |
404 | {"error": "unknown_issuer", "error_detail": "we don't serve that domain"} | — |
An expired token means refresh; an ended session (revoked, or rotated out by a tenant switch) means sign in again.
Errors
Outside the OAuth endpoints, errors are JSON with a machine-readable code and a human sentence:
{ "error": "not_found", "error_detail": "no such tenant" }
Branch on error; error_detail is for people and may change. The OAuth
endpoints use their RFCs’ shape instead: error alone on /oauth/token and
/oauth/revoke, error plus error_description on /oauth/register.
| Status | Meaning |
|---|---|
400 | The request is malformed; error_detail says how. |
401 | No valid token. |
403 | You can see it but may not change it, for example a tenant you are a member of but do not own. |
404 | Not found, or not yours. The two are deliberately the same answer, and an id of the wrong kind is a 404 too. |
409 | The request conflicts with current state, for example deleting an org’s last tenant. |
429 | Rate limited. |
Pagination
The lists in this tab return the whole collection as a plain JSON array. The
one endpoint that pages, an org’s audit log, takes
per_page (1, 50 or 100, default 50) and page (from 0), and
returns an RFC 5988 Link header with first, prev, next and last, each
omitted when it would point nowhere. No header means a single page.