OAuth 2.1
SuperSpace runs a standard OAuth 2.1 authorization server. Use it for third-party apps acting on a user's behalf; use API keys for first-party / server-to-server integrations.
OAuth access tokens are never admin and are fail-closed: an endpoint that does not declare a scope is unavailable via OAuth, and a token missing a required scope is rejected. Session and API-key credentials bypass scope checks entirely.
Authorization Server Metadata
Metadata is per-brand, derived from the request host.
GET /.well-known/oauth-authorization-server
Returned Params
- issuer: String
- authorization_endpoint: String |
https://<host>/oauth/authorize - token_endpoint: String |
https://<host>/oauth/token - revocation_endpoint: String |
https://<host>/oauth/revoke - introspection_endpoint: String |
https://<host>/oauth/introspect - registration_endpoint: String |
https://<host>/oauth/registration - response_types_supported: Array |
["code"] - grant_types_supported: Array |
["authorization_code", "refresh_token"] - code_challenge_methods_supported: Array |
["S256"] - token_endpoint_auth_methods_supported: Array |
["client_secret_basic", "client_secret_post", "none"] - scopes_supported: Array | see Scopes
- service_documentation: String
Flows
- Grant types:
authorization_codeandrefresh_tokenonly. - PKCE is mandatory (
S256only). - Refresh tokens rotate — the previous refresh token is revoked on use. Rotation only advances once the new access token is validated against the brand, trial, and role checks.
- No OpenID Connect — there is no
/.well-known/openid-configuration, no userinfo, no ID tokens. The OIDC gem is wired only for Dynamic Client Registration. - Brand isolation — a token is bound to the brand (hostname) it was issued under and is rejected on any other brand. The authorize screen only lists the user's non-trial accounts on the current brand.
Token revocation on role change
Removing a user's role on an account revokes their OAuth tokens (and sessions) for that account.
Dynamic Client Registration (DCR)
POST /oauth/registration (RFC 7591)
Only public PKCE clients are allowed — token_endpoint_auth_method must be
"none".
Errors
- 400
{"error":"invalid_client_metadata", ...}|token_endpoint_auth_methodis not"none" - 429
{"error":"too_many_requests", ...}| per-IP limit of 50 registrations / hour exceeded
Scopes
There are no default scopes — token presence is identity, and every resource scope is opt-in.
| Scope | Grants |
|---|---|
sites:read |
Site reads + nested site reads (show/index, backups list, cache status, CDN status, variants list, tasks, edge-rules list, Shield reads, logs, metrics) |
sites:write |
Site + nested site writes (create/update/destroy, backups, cache, restart, restore, edge rules, Shield writes, certificates, site-domain CRUD, variant change) |
domains:read |
Domains list/show/query/available; domain-registration index/show/check/suggestions; contacts/hosts/processes reads |
domains:write |
Domain-registration mutations; domain-contact create/update/destroy/resend; host/process writes |
dns:read |
DNS zones index/show/dns_stats; DNS records index/show |
dns:write |
DNS zone & record create/update/destroy |
billing:read |
Orders index/show; subscriptions index/show; carts show |
GET /api/about accepts any valid token regardless of scope.
Endpoints Unavailable via OAuth
These declare no scope and are fail-closed — they require a session or
API-key credential and return 403 endpoint not available via OAuth for any
OAuth token:
- Account CRUD & account roles
- API keys
- Users and
user_roles - The global tasks endpoints (
GET /api/tasks/:id) - SSO (per-site and top-level)
- Task result reporting (
POST /api/webhooks/task/{task-id}) - The domain-order endpoints:
POST /api/orders/domainandPOST /api/domain_registrations/:id/registrant_change(both place a paid order and require a user-scoped API key —registrant_changeis deliberately excluded from thedomains:writescope) - All write operations on orders/sites-billing (there is intentionally no
billing:writescope)
Scope Enforcement Errors
Errors
- 401
{"error":"invalid_token","error_description":"token audience is not valid for /api"}| the token carries aresourceaudience (RFC 8707) minted for another resource (e.g. the MCP server) —/apirejects it before the scope check - 403
{"error":"insufficient_scope","error_description":"requires scope: <required>"}| with headerWWW-Authenticate: Bearer error="insufficient_scope", scope="<required>" - 403
{"error":"insufficient_scope","error_description":"endpoint not available via OAuth"}| endpoint declares no scope