OAuth & scopes
Sunny is its own OAuth 2.1 authorization server. Every client — the MCP server, a CLI, a third-party integration — authorizes the same way: authorization code + PKCE, always with explicit user consent.
Discovery
Section titled “Discovery”The authorization server lives on the auth host, https://auth.sunnysdk.com. Hand-configure
these, or discover them from the root aliases:
| Endpoint | Purpose |
|---|---|
/.well-known/oauth-authorization-server |
AS metadata (RFC 8414) — authorize/token/registration/JWKS URLs, supported scopes. |
/.well-known/openid-configuration |
The same metadata under the OIDC-conventional path. |
/api/auth/oauth2/authorize |
Authorization endpoint. |
/api/auth/oauth2/token |
Token endpoint. |
/api/auth/oauth2/register |
RFC 7591 Dynamic Client Registration — still supported, but prefer CIMD (below) for a new MCP or agent client. |
A 401 from a resource server (the MCP endpoint, say) points back at the protected-resource metadata, which in turn names the authorization server.
Always pass resource on the authorize request (RFC 8707), set to the resource you’re
actually calling. Without an audience the server issues an opaque access token, and every
Sunny resource server rejects it as invalid_token — they verify JWTs only. With it, you get a
JWT carrying your granted scopes.
| Calling | resource value |
|---|---|
| REST API | https://api.sunnysdk.com |
| MCP server | https://agents.sunnysdk.com/mcp |
Registering a client
Section titled “Registering a client”Client ID Metadata Documents (CIMD) are the preferred way to register — use this for a new MCP
or agent client. Instead of POSTing to the registration endpoint, host a public HTTPS URL that
serves a JSON Client ID Metadata Document, then use that URL itself as your OAuth client_id.
Sunny follows
draft-ietf-oauth-client-id-metadata-document-02
with the MCP 2026-07-28 profile applied, which additionally requires client_name and
redirect_uris in the document.
{ "client_id": "https://your-agent.example/oauth-client.json", "client_name": "Your Agent", "redirect_uris": ["https://your-agent.example/callback"], "token_endpoint_auth_method": "none"}The client_id field in the document must exactly equal the URL it’s served from. On first use,
the authorization server fetches, validates, and caches the document — no separate registration
call, no client secret to provision or leak. redirect_uris in the document are matched by exact
string at authorize time, same as a DCR-registered client — with one carve-out: a loopback-IP
entry like http://127.0.0.1/callback matches any port (RFC 8252 §7.3), so a native client using
an ephemeral local port should register the 127.0.0.1 form; localhost gets no port variance
and must match exactly. Only token_endpoint_auth_method of none (public client, the default)
or private_key_jwt is allowed for a CIMD client — a shared client secret isn’t possible with
this method.
A CIMD client is still untrusted: the user consent screen and PKCE are required exactly as they
are for a DCR-registered client — CIMD only changes how the client identifies itself, not how
much it’s trusted. The authorization server advertises support in its metadata as
client_id_metadata_document_supported: true.
/api/auth/oauth2/register (RFC 7591 DCR) stays available as a fallback for clients that
pre-register a client id instead — MCP’s 2026-07-28 spec deprecates DCR with a 12-month-plus
window, so it isn’t going away soon, but a new client should reach for CIMD first.
Scopes
Section titled “Scopes”Every scope Sunny issues, and what it grants:
| Scope | Category | Grants |
|---|---|---|
openid | Account / identity | Confirm your Sunny identity. |
profile | Account / identity | Your display name and avatar. |
email | Account / identity | Your email address and whether it is verified. |
offline_access | Account / identity | Keep you signed in without re-approving each time. |
projects:read | Action | Read your projects, floors, and spaces. |
projects:write | Action | Create and update projects, floors, and spaces on your behalf. |
quotes:read | Action | Read your quotes, customers, and discount tables. |
quotes:write | Action | Create and update quotes, customers, and discount tables on your behalf. Discount-table edits still require workspace owner or admin. Granted only to apps Sunny has approved for it, or to workspace API keys.elevated |
quote_costs:read | Action | See your dealer cost and margin on quotes: buy prices, margin, and the discount rules applied. Granted only to apps Sunny has approved for it, or to workspace API keys.elevated |
orders:write | Action | Turn configured-order files (Order SIF or OFDA XML) into draft quotes on your behalf. Granted only to apps Sunny has approved for it, or to workspace API keys.elevated |
arrange | Action | Start AI room arrangements on your behalf. |
workflows:run | Action | Start and cancel published render workflows on your behalf, spending credits for each image. |
workflows:read | Action | List published render workflows and read the runs and images you started. |
catalog:read | Action | Read catalog items and product data. |
catalog:write | Action | Create and edit catalog items, typicals, photos and model assets, and see every catalog entry including dealer-only ones. Granted only to apps Sunny has approved for it, or to workspace API keys.elevated |
docs:read | Action | Read distilled product documentation: family composition rules, assembly guidance, and editorial notes.elevated |
embeds:write | Action | Mint, list, update, and revoke embeddable widgets for your catalog items.elevated |
embeds:read | Action | Read embeddable-widget listings and analytics for your catalog items.elevated |
webhooks:manage | Action | Register, update, and monitor webhook endpoints for your workspace.elevated |
org:admin | Action | Perform workspace owner/admin operations, such as editing discount tables. Granted only on workspace API keys.elevated |
audit:read | Action | See who changed what in your workspace: API keys, quotes, customers, discount tables, and webhook endpoints.elevated |
admin | Admin | Perform Sunny staff administration across all workspaces.elevated |
A newly registered client is granted every scope except the elevated ones: quotes:write, quote_costs:read, orders:write, catalog:write, docs:read, embeds:write, embeds:read, webhooks:manage, org:admin, audit:read, and admin.
The admin scope is reserved for Sunny staff; anyone else who requests it receives the other
scopes without it. Ask for only what you need.
API keys
Section titled “API keys”For server-to-server use without a browser sign-in, send an API key in the x-api-key header
instead of a Bearer token. Personal keys are created under Account → API keys; workspace
keys, which belong to the workspace rather than a person, under Organization → API keys.
Consent
Section titled “Consent”The user sees a consent screen before every token is issued. Sunny never silently re-consents an existing grant, even for a client the user has approved before.
Revoking
Section titled “Revoking”Users can review and revoke any authorized application under Account → Connected apps. Revoking invalidates that client’s tokens immediately.
Token lifetime
Section titled “Token lifetime”By default a token is bound to the browser session that authorized it — it stops working once
that session ends. An agent or background client that needs a refresh token surviving past the
authorizing session should request the offline_access scope; only an offline_access refresh
token survives session end.
Rate limits
Section titled “Rate limits”The cost-bearing endpoints — arrangement, floor planning, preview render, and minting a single-item embed — allow 5 requests per 60 seconds per caller. That’s generous for real runs (which can take up to 160 seconds) while still stopping a scripted hammer. For Bearer callers the limit is counted per OAuth client and user, so rotating tokens doesn’t dodge it, and two different apps the same user authorized don’t share one budget.
Every other API request made with a Bearer token or an API key is limited to 600 reads
(GET, HEAD) and 120 writes per 60 seconds. The cost limit above applies on top of these.
API-key limits are counted per user for personal keys and per workspace for workspace keys.
Retry-After: 60