Authentication#
Every /api/v1/* request — except /api/v1/report (anonymous bug submission) — requires an API key. Quota is per user account, shared across all your keys (up to 20 active).
Key prefixes#
| Prefix | Purpose | Browser-safe (CORS) |
|---|---|---|
qb_pk_* | Publishable — safe to embed in client bundles (browsers, mobile apps). | Yes — sends Access-Control-Allow-Origin: * |
qb_sk_* | Secret — backend only. Don’t ship in client bundles. | No — browsers will block cross-origin use |
Passing the key#
Send your key in the X-API-Key header. Authorization: Bearer <key> also works.
Rotation#
Rotate keys from the dashboard. When you rotate:
- Create the new key first.
- Deploy the new key alongside the old one in your environment.
- Delete the old key after your traffic has drained to the new key (check dashboard usage).
The rotated old key keeps working for 24h as a grace period — no downtime, no broken requests.
Storage#
- Server: environment variables (
QUIZBASE_KEY), never committed files. - Client (browser/mobile): only
qb_pk_*. Consider a server-side proxy if you want to hide the key entirely — it costs you a hop but stops key extraction. - CI: encrypted secrets (GitHub Actions, GitLab CI, Railway variables).
Public endpoint (no key needed)#
One endpoint is public — anonymous-by-design with per-IP rate limit:
POST /v1/report— translation / factual / attribution / inappropriate problem reports. Rate-limited 5/min per IP.
Every other endpoint requires an API key.
CORS — when browser fetch works#
/v1/report: open CORS — fetch from any origin works without a key.- Authenticated endpoints with
qb_pk_*: CORS header set. Browser fetch from any origin works — that’s what publishable keys are for. - Authenticated endpoints with
qb_sk_*: no CORS header. Browsers block these requests by design. Secret keys aren’t meant to ship in browser bundles.
OPTIONS preflight returns 204 with Access-Control-Allow-Methods: GET, POST, OPTIONS and Allow-Headers: Authorization, Content-Type, X-API-Key, X-Request-Id.
There is no per-key origin allowlist today — any origin can use a pk_* key. Rotate the key (and optionally revoke the old one) if you need to invalidate it.
OAuth 2.1 (for AI agents on /mcp)#
QuizBase implements OAuth 2.1 with Dynamic Client Registration for the MCP server. This is the path Claude.ai web/desktop/mobile Custom Connectors, ChatGPT custom connectors, Gemini agents, and MCP Inspector take by default — the surface handles the flow without exposing a key to the user.
API keys (qb_pk_* / qb_sk_*) remain the authentication for /api/v1/* REST endpoints. The rule is OAuth for agents, API key for code.
Discovery endpoints#
| Endpoint | What it returns |
|---|---|
/.well-known/oauth-protected-resource | RFC 9728: resource, authorization_servers, scopes_supported, bearer_methods_supported, signing algs |
/.well-known/oauth-authorization-server | RFC 8414: authorization_endpoint, token_endpoint, registration_endpoint, jwks_uri, code_challenge_methods_supported: ["S256"], grant types, scopes |
/api/auth/jwks | JWKS — EdDSA Ed25519 public keys for verifying access tokens locally |
Scopes#
| Scope | Grants |
|---|---|
openid | OIDC standard — emits sub (user id) |
profile | User’s display name |
email | User’s email address |
offline_access | Refresh tokens (required for long-lived agent sessions) |
quizbase:read | Read every public-data tool on /mcp (quizbase_random, quizbase_list, …, quizbase_report) |
Token lifetime#
- Access token (JWT, EdDSA-signed): 1 hour. Sent as
Authorization: Bearer <jwt>to/mcp. The token carriesaud = https://quizbase.runriva.com/mcp— sending it to any other audience fails verification. - Refresh token: 90 days, rotates on every use. AI agent sessions can last weeks, so we lean long; rotation contains the blast radius.
Revoking an OAuth grant#
To revoke access for a specific app:
- Note the client name shown on the consent screen (e.g. “Claude”, “MCP Inspector”).
- Email quizbase@gmail.com with the client name and your account email. We confirm and revoke within one business day.
Next#
- Errors and retries — what a 401 looks like and how to handle it
- GET /v1/me — first authenticated endpoint to smoke-test your key
- GET /v1/questions/random — fetch a quiz round
- MCP guide — step-by-step setup for OAuth + Bearer paths