{"openapi":"3.1.0","info":{"title":"Wavegrain","version":"1.0.0","description":"Wavegrain's public interface is its MCP server: one endpoint, `POST /api/mcp`, that AI agents and automation tools call with an access key or an OAuth token.\n\nThis document covers that endpoint, the OAuth server hosted connectors use, the signed links some tools return, and the health probe.\n\nThe tools themselves are listed with their inputs and outputs at /developers/mcp-tools.\n\nWavegrain has no public REST API for slideshows yet."},"servers":[{"url":"/"}],"tags":[{"name":"MCP"},{"name":"OAuth"},{"name":"Signed links"},{"name":"Status"}],"paths":{"/api/mcp":{"post":{"tags":["MCP"],"summary":"Send an MCP message","description":"The Wavegrain MCP server over Streamable HTTP. It is stateless: no session id, and every request carries the bearer token.\n\nSend `Accept: application/json, text/event-stream`; a request that does not accept both gets 406. A request on protocol revision 2025-11-25 or older gets its answer as one server-sent `message` event. No `initialize` call is needed first.\n\nA request may carry at most 10 messages. Each access key may send 120 messages a minute and each account 240.\n\nA browser request whose `Origin` is not Wavegrain's own is refused.\n\nThe tools, their inputs and their outputs are in the MCP tool reference at /developers/mcp-tools.","operationId":"postApiMcp","parameters":[{"name":"MCP-Protocol-Version","in":"header","description":"The protocol revision the client speaks, such as `2025-11-25`.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","description":"A JSON-RPC 2.0 message as the Model Context Protocol defines it, such as `initialize`, `tools/list` or `tools/call`.","required":["jsonrpc","method"],"properties":{"jsonrpc":{"const":"2.0"},"id":{"type":["string","integer"]},"method":{"type":"string","examples":["tools/call"]},"params":{"type":"object","examples":[{"name":"list_slideshows","arguments":{"limit":5}}]}}},{"type":"array","items":{"type":"object","description":"A JSON-RPC 2.0 message as the Model Context Protocol defines it, such as `initialize`, `tools/list` or `tools/call`.","required":["jsonrpc","method"],"properties":{"jsonrpc":{"const":"2.0"},"id":{"type":["string","integer"]},"method":{"type":"string","examples":["tools/call"]},"params":{"type":"object","examples":[{"name":"list_slideshows","arguments":{"limit":5}}]}}},"maxItems":10}]}}}},"responses":{"200":{"description":"The JSON-RPC answer. A tool that refuses answers 200 with `isError: true` and a plain sentence."},"400":{"description":"A malformed message, or more than 10 messages in one request."},"401":{"description":"No token, or a malformed, unknown, revoked or expired one. `WWW-Authenticate` names the protected resource metadata URL."},"403":{"description":"The token lacks the tool's permission (`WWW-Authenticate: Bearer error=\"insufficient_scope\"`), or the Origin is not allowed."},"406":{"description":"The request does not accept both `application/json` and `text/event-stream`."},"413":{"description":"The body is larger than 256 KiB."},"429":{"description":"Over the rate limit. `Retry-After` says how many seconds to wait."}},"security":[{"accessKey":[]},{"oauth":[]}],"x-auth":"mcp"},"get":{"tags":["MCP"],"summary":"Not supported (405)","description":"A stateless server opens no server-to-client stream. Use POST.","operationId":"getApiMcp","responses":{"405":{"description":"Method not allowed. `Allow: POST`."}},"security":[],"x-auth":"none"},"delete":{"tags":["MCP"],"summary":"Not supported (405)","description":"A stateless server has no session to end. Use POST.","operationId":"deleteApiMcp","responses":{"405":{"description":"Method not allowed. `Allow: POST`."}},"security":[],"x-auth":"none"}},"/.well-known/oauth-protected-resource/api/mcp":{"get":{"tags":["OAuth"],"summary":"Protected resource metadata for the MCP endpoint (RFC 9728)","description":"The 401 from `/api/mcp` points here. It names the authorization server and the scopes.","operationId":"getWellKnownOauthProtectedResourceApiMcp","responses":{"200":{"description":"The metadata document."}},"security":[],"x-auth":"none"}},"/.well-known/oauth-protected-resource":{"get":{"tags":["OAuth"],"summary":"Protected resource metadata at the root (RFC 9728)","description":"The same document, for clients that look at the origin first.","operationId":"getWellKnownOauthProtectedResource","responses":{"200":{"description":"The metadata document."}},"security":[],"x-auth":"none"}},"/.well-known/oauth-authorization-server":{"get":{"tags":["OAuth"],"summary":"Authorization server metadata (RFC 8414)","description":"Endpoints, scopes, PKCE method S256, and public clients only (`token_endpoint_auth_method: none`).","operationId":"getWellKnownOauthAuthorizationServer","responses":{"200":{"description":"The metadata document."}},"security":[],"x-auth":"none"}},"/oauth/register":{"post":{"tags":["OAuth"],"summary":"Register a client (RFC 7591)","description":"Dynamic client registration for public clients. Rate limited per network address.","operationId":"postOauthRegister","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["redirect_uris"],"properties":{"redirect_uris":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"string","format":"uri"},"description":"https, or http on a loopback address, with no fragment."},"client_name":{"type":"string","maxLength":100},"grant_types":{"type":"array","items":{"enum":["authorization_code","refresh_token"]}},"response_types":{"type":"array","items":{"const":"code"}}}}}}},"responses":{"201":{"description":"The registered client, with `client_id`."},"400":{"description":"An OAuth error: `{ error, error_description }`."},"429":{"description":"Too many registrations from this address."}},"security":[],"x-auth":"none"}},"/oauth/token":{"post":{"tags":["OAuth"],"summary":"Exchange a code or refresh a token","description":"`authorization_code` with PKCE, or `refresh_token` with rotation. Access tokens start with `wg_oat_` and last 60 minutes.","operationId":"postOauthToken","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["grant_type","client_id"],"properties":{"grant_type":{"enum":["authorization_code","refresh_token"]},"client_id":{"type":"string"},"code":{"type":"string"},"code_verifier":{"type":"string"},"redirect_uri":{"type":"string"},"refresh_token":{"type":"string"},"resource":{"type":"string","description":"The MCP endpoint URL (RFC 8707). Optional."}}}}}},"responses":{"200":{"description":"`{ access_token, token_type: \"Bearer\", expires_in, refresh_token, scope }`"},"400":{"description":"An OAuth error: `{ error, error_description }`."}},"security":[],"x-auth":"none"}},"/oauth/revoke":{"post":{"tags":["OAuth"],"summary":"Revoke a token (RFC 7009)","description":"Revoking either token of a grant disconnects the whole grant.","operationId":"postOauthRevoke","requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["token","client_id"],"properties":{"token":{"type":"string"},"client_id":{"type":"string"},"token_type_hint":{"enum":["access_token","refresh_token"]}}}}}},"responses":{"200":{"description":"Revoked, or the token was already unknown."},"400":{"description":"An OAuth error: `{ error, error_description }`."}},"security":[],"x-auth":"none"}},"/api/export/{sessionId}":{"get":{"tags":["Signed links"],"summary":"Download an export ZIP through a signed link","description":"The `export_slideshow` tool returns this link. It lasts ten minutes and needs no other credential.","operationId":"getApiExportSessionId","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"download","in":"query","required":true,"description":"The signed token from `export_slideshow`.","schema":{"type":"string"}}],"responses":{"200":{"description":"The ZIP file."},"302":{"description":"A redirect to the stored ZIP."},"403":{"description":"The link is invalid or expired."},"404":{"description":"No such export."}},"security":[],"x-auth":"signedLink"}},"/api/tailored/uploads/{id}":{"put":{"tags":["Signed links"],"summary":"Upload an image to a signed upload slot","description":"The `create_upload` tool returns this slot. Send the image bytes as the body. The slot is single use.","operationId":"putApiTailoredUploadsId","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"token","in":"query","required":true,"description":"The signed token from `create_upload`.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"image/*":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"The stored image."},"400":{"description":"Not an image, or too large."},"403":{"description":"The slot is invalid, used or expired."},"429":{"description":"Too many uploads."}},"security":[],"x-auth":"signedLink"}},"/api/health":{"get":{"tags":["Status"],"summary":"Health probe","description":"Whether the app, its database and its queue answer.","operationId":"getApiHealth","responses":{"200":{"description":"Healthy."},"503":{"description":"A dependency is down."}},"security":[],"x-auth":"none"}}},"components":{"securitySchemes":{"accessKey":{"type":"http","scheme":"bearer","description":"An access key made in Settings, under Connect an AI agent. It starts with `wg_pat_`."},"oauth":{"type":"oauth2","description":"OAuth 2.1 with PKCE and dynamic client registration, for hosted connectors such as claude.ai and ChatGPT. Access tokens start with `wg_oat_`.","flows":{"authorizationCode":{"authorizationUrl":"/oauth/authorize","tokenUrl":"/oauth/token","refreshUrl":"/oauth/token","scopes":{"read":"Read brands, formats, slideshows, the Calendar, collections and credits.","write":"Change saved work without spending credits.","generate":"Spend credits, within each call's limit and the key's daily limit."}}}},"sessionCookie":{"type":"apiKey","in":"cookie","name":"sb-auth-token","description":"The browser session of the Wavegrain app. Internal only."}}}}