OAuth 2.1 for the SpeakNotes API
Authorization code with PKCE, refresh token rotation, dynamic client registration, and revocation. Use it when your app acts on behalf of other SpeakNotes users.
Discovery
Both metadata documents are public. MCP clients find them automatically from the 401 challenge the API returns.
# Authorization server metadata (RFC 8414) curl https://speaknotes.io/.well-known/oauth-authorization-server # Protected resource metadata (RFC 9728) curl https://api.speaknotes.io/.well-known/oauth-protected-resource
The flow
- 1
Register a client
POST to the registration endpoint with your redirect URIs. Public clients use PKCE and get no secret; confidential clients get one. https, loopback http, and custom app schemes are all accepted.
curl -X POST https://api.speaknotes.io/oauth/register \ -H "Content-Type: application/json" \ -d '{ "client_name": "Your app", "redirect_uris": ["https://your.app/callback"], "token_endpoint_auth_method": "none", "scope": "notes:read notes:write summaries:write" }' - 2
Send the user to authorize
Open the authorization endpoint with your client id, redirect URI, scopes, state, and an S256 code challenge. The user signs in, sees exactly which scopes you asked for, and approves or declines.
https://speaknotes.io/oauth/authorize ?client_id=sn_client_... &redirect_uri=https://your.app/callback &response_type=code &scope=notes:read%20summaries:write &state=RANDOM &code_challenge=BASE64URL_SHA256_OF_VERIFIER &code_challenge_method=S256
- 3
Exchange the code
POST the code and your code verifier to the token endpoint. Codes are single use and expire in a minute; replaying one revokes the tokens it produced.
curl -X POST https://api.speaknotes.io/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "authorization_code", "client_id": "sn_client_...", "code": "...", "redirect_uri": "https://your.app/callback", "code_verifier": "..." }' - 4
Refresh
Access tokens last an hour. Refresh tokens rotate on every use, and presenting a revoked one drops the whole grant, which is what stops a stolen token being useful.
curl -X POST https://api.speaknotes.io/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "refresh_token", "client_id": "sn_client_...", "refresh_token": "sn_rt_..." }'
access_token: 1h · refresh_token: 60d, rotating · code: 60s, single use
Requesting scopes
Ask for the least you need. A refresh may narrow scope but never widen it, and users can see and revoke any app from their settings.
Revocation
POST either token to the revocation endpoint. Revoking one half kills the pair. Revoking a token that never existed is still a success, per RFC 7009.
curl -X POST https://api.speaknotes.io/oauth/revoke \
-H "Content-Type: application/json" \
-d '{"client_id": "sn_client_...", "token": "sn_rt_..."}'MCP clients
An MCP client needs none of this by hand. It calls a tool, gets a 401 pointing at the resource metadata, registers itself, and walks the user through the same consent screen.