Token Endpoints
POST /token/exchange
Exchange a one-time authorization code (delivered to a web/https redirect_uri by GET /login) for a token pair. Web apps use this instead of reading tokens from the callback URL.
Request:
{
"code": "one-time-code-from-callback",
"app_id": "uuid",
"code_verifier": "original-pkce-verifier"
}
code_verifier is required only if you sent a code_challenge on the /login request.
Success Response (200):
{
"access_token": "eyJhbG...",
"refresh_token": "a1b2c3...",
"token_type": "Bearer",
"expires_in": 900
}
The code is single-use and expires 60 seconds after issuance.
Errors: | Status | Detail | Meaning | |--------|--------|---------| | 400 | Invalid or expired code | Code not found, already used, or older than 60s | | 400 | code_verifier required / mismatch | PKCE challenge was set but verifier missing or wrong | | 400 | Code does not belong to this app | app_id mismatch |
POST /token/refresh
Exchange a valid refresh token for a new access + refresh token pair. The old refresh token is revoked (rotation).
Request:
{
"refresh_token": "a1b2c3d4...",
"app_id": "uuid"
}
Success Response (200):
{
"access_token": "eyJhbG...",
"refresh_token": "new-token...",
"token_type": "Bearer",
"expires_in": 900
}
Errors: | Status | Detail | Meaning | |--------|--------|---------| | 401 | Invalid or expired refresh token | Token not found, expired, or already used | | 401 | Token does not belong to this app | app_id mismatch | | 403 | Account suspended | User suspended for this app |
Refresh tokens rotate on every use — the returned refresh token replaces the one you sent. Store it securely server-side.
Replay Detection: Reuse of a revoked refresh token is treated as compromise and revokes the entire token family (ALL refresh tokens for that user+app).
POST /token/revoke
Revoke a refresh token (logout).
Request:
{
"refresh_token": "a1b2c3d4..."
}
Response (200):
{ "status": "ok" }
Always returns 200, even if the token doesn't exist (prevents information leakage).
POST /token/verify
Verify an access token and return its claims. For apps that can't do local JWT verification.
Request:
{
"token": "eyJhbG...",
"audience": "your-app-id"
}
audience is required — pass your app_id. It is checked against the token's aud claim. This endpoint validates user access tokens only; service tokens (token_type: "service") presented as user tokens are rejected with valid: false.
Valid Response:
{
"valid": true,
"claims": {
"sub": "user-uuid",
"email": "user@example.com",
"roles": ["user", "admin"],
"aud": "app-uuid",
"iss": "https://keymaster.cloud-monitor.com",
"exp": 1710548100,
"token_type": "access"
}
}
Invalid Response:
{
"valid": false,
"error": "Token has expired"
}
Note: Prefer local JWKS verification for production. This endpoint is for debugging and apps that can't handle JWTs locally.
POST /auth/token
OAuth2 token endpoint. Currently supports client_credentials grant.
Request (form-encoded):
grant_type=client_credentials
&client_id=app-uuid
&client_secret=your-secret
&scope=push:send
Requests may also be sent as JSON with the same fields. scope is space-separated.
Success Response (200):
{
"access_token": "eyJhbG...",
"token_type": "bearer",
"expires_in": 900,
"scope": "push:send"
}
The returned JWT carries token_type: "service" and a scope claim, and has no aud claim. Service tokens are not accepted as user tokens.
The app is only granted scopes it is entitled to: each requested scope must be listed in the app's config["service_scopes"] in Keymaster, otherwise the request returns 400 invalid_scope.
Errors:
| Status | Detail |
|--------|--------|
| 400 | unsupported_grant_type |
| 400 | invalid_scope: xyz (unknown scope, or one the app is not entitled to) |
| 401 | invalid_client |
| 429 | too_many_requests (10 attempts/15min per client_id) |
GET /.well-known/jwks.json
Public keys for JWT verification. Cache these and refresh hourly or when kid doesn't match.
Response:
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "a1b2c3d4e5f6",
"n": "base64url-encoded-modulus...",
"e": "AQAB"
}
]
}
GET /.well-known/openid-configuration
OIDC discovery document.
Response:
{
"issuer": "https://keymaster.cloud-monitor.com",
"authorization_endpoint": "https://keymaster.cloud-monitor.com/login",
"token_endpoint": "https://keymaster.cloud-monitor.com/auth/token",
"userinfo_endpoint": "https://keymaster.cloud-monitor.com/userinfo",
"jwks_uri": "https://keymaster.cloud-monitor.com/.well-known/jwks.json",
"revocation_endpoint": "https://keymaster.cloud-monitor.com/token/revoke",
"response_types_supported": ["code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"scopes_supported": ["openid", "profile", "email", "roles", "push:send"],
"token_endpoint_auth_methods_supported": ["client_secret_post"],
"grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"]
}