OAuth 2.1
Sealmetrics runs a full OAuth 2.1 authorization server. Use it when your application or AI assistant needs to read someone else's analytics — the user approves the connection in the Sealmetrics dashboard and you never touch their password or API key.
This is the mechanism behind the remote MCP server: claude.ai and ChatGPT register themselves dynamically, the user approves one site, and the assistant gets a read-only token.
- Reading your own account from a script or agent → use an API key. Much simpler.
- Reading your users' accounts from your product → OAuth 2.1, this page.
- You already hold a dashboard session → JWT bearer tokens.
Discovery
The server publishes RFC 8414 metadata at the issuer root. Fetch it and drive everything from there rather than hard-coding endpoints:
curl -s https://my.sealmetrics.com/.well-known/oauth-authorization-server
{
"issuer": "https://my.sealmetrics.com",
"authorization_endpoint": "https://my.sealmetrics.com/api/v1/oauth/authorize",
"token_endpoint": "https://my.sealmetrics.com/api/v1/oauth/token",
"registration_endpoint": "https://my.sealmetrics.com/api/v1/oauth/register",
"revocation_endpoint": "https://my.sealmetrics.com/api/v1/oauth/revoke",
"response_types_supported": ["code"],
"response_modes_supported": ["query"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"],
"scopes_supported": ["analytics:read", "offline_access"]
}
The response is cacheable for one hour (Cache-Control: public, max-age=3600).
Scopes
| Scope | Grants |
|---|---|
analytics:read | Read-only access to the approved site's analytics. This is the only resource scope. |
offline_access | Also issue a refresh token, so the connection survives beyond the access token's lifetime. |
There is no write scope. An OAuth-connected app cannot modify anything in the user's account.
Client model
Sealmetrics supports public clients only — token_endpoint_auth_method is none, there is no client secret, and PKCE with S256 is required. This matches how MCP clients and native/SPA apps work.
Step 1 — Register your client
RFC 7591 dynamic client registration. No credentials needed; the endpoint is rate-limited per IP.
POST/oauth/register
curl -X POST https://my.sealmetrics.com/api/v1/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "Acme Reporting",
"redirect_uris": ["https://acme.example/oauth/callback"],
"token_endpoint_auth_method": "none"
}'
| Field | Type | Required | Notes |
|---|---|---|---|
redirect_uris | string[] | Yes | 1–10 URIs. The redirect_uri you send later must match one exactly. |
client_name | string | No | Shown to the user on the consent screen. Defaults to MCP client. |
token_endpoint_auth_method | string | No | Only none (public client) is supported. |
Returns 201 Created with the registered client metadata, including the client_id. Unknown RFC 7591 metadata fields are ignored rather than rejected.
Step 2 — Send the user to authorize
GET/oauth/authorize
Generate a PKCE verifier and challenge, then redirect the user's browser:
https://my.sealmetrics.com/api/v1/oauth/authorize
?client_id=<client_id>
&redirect_uri=https%3A%2F%2Facme.example%2Foauth%2Fcallback
&response_type=code
&scope=analytics%3Aread%20offline_access
&state=<random-csrf-value>
&code_challenge=<BASE64URL(SHA256(verifier))>
&code_challenge_method=S256
| Parameter | Required | Notes |
|---|---|---|
client_id | Yes | From step 1. |
redirect_uri | Yes | Must exactly match one of the registered URIs. |
response_type | Yes | Must be code. Anything else returns unsupported_response_type. |
scope | No | Space-separated. Defaults to read-only if omitted. |
state | No | Strongly recommended — your CSRF token, returned unchanged. |
code_challenge | Yes | S256 challenge derived from your verifier. |
code_challenge_method | Yes | S256. plain is not supported. |
resource | No | RFC 8707 resource indicator. |
Sealmetrics redirects the user to the dashboard consent screen, where they log in (or sign up) and pick which site to share. On approval the browser comes back to your redirect_uri with code and state.
If client_id or redirect_uri fails validation the server renders a 400 instead of redirecting. Redirecting an unvalidated URI would turn the endpoint into an open redirect.
Pending authorization requests expire after 30 minutes, which leaves room for the user to create an account mid-flow.
Step 3 — Exchange the code for tokens
POST/oauth/token
Form-encoded, per RFC 6749.
curl -X POST https://my.sealmetrics.com/api/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=<code>" \
-d "code_verifier=<verifier>" \
-d "redirect_uri=https://acme.example/oauth/callback" \
-d "client_id=<client_id>"
{
"access_token": "…",
"refresh_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "analytics:read offline_access"
}
Token responses are sent with Cache-Control: no-store.
Lifetimes: authorization codes are valid for 5 minutes, access tokens for 1 hour, refresh tokens for 30 days.
Refreshing
curl -X POST https://my.sealmetrics.com/api/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=<refresh_token>" \
-d "client_id=<client_id>"
grant_type values other than authorization_code and refresh_token return unsupported_grant_type.
Step 4 — Call the API
Use the access token as a bearer token against any read endpoint:
curl "https://my.sealmetrics.com/api/v1/stats/overview?account_id=ACCOUNT&period=30d" \
-H "Authorization: Bearer <access_token>"
Revoking
POST/oauth/revoke
RFC 7009. Revoking a refresh token tears down the whole grant.
curl -X POST https://my.sealmetrics.com/api/v1/oauth/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=<token>" \
-d "token_type_hint=refresh_token"
Always returns 200, whether or not the token existed — per the RFC, so the endpoint cannot be used to probe token validity.
Users can also revoke from the dashboard at any time: My Account → Connected Apps. See Connected Apps.
Managing connections from the dashboard
These endpoints back the Connected Apps screen and authenticate with the dashboard session, not with an OAuth token:
| Endpoint | Method | Description |
|---|---|---|
/oauth/connections | GET | List the user's active grants |
/oauth/connections/{grant_id} | DELETE | Revoke one grant |
/oauth/consent/{request_id} | GET | Details of a pending authorization request |
/oauth/consent/{request_id}/decision | POST | Approve or deny, and choose the site |
/oauth/onboarding | POST | Complete signup inside the consent flow |
Error format
OAuth endpoints use the RFC 6749 error shape, not the standard Sealmetrics error envelope:
{
"error": "invalid_grant",
"error_description": "Authorization code is expired or already used"
}
error | Meaning |
|---|---|
invalid_request | A required parameter is missing or malformed |
invalid_client | Unknown client_id |
invalid_redirect_uri | The redirect_uri does not match a registered URI |
invalid_grant | Code or refresh token is invalid, expired, already used, or the PKCE verifier does not match |
invalid_scope | A requested scope is not in scopes_supported |
unsupported_response_type | response_type is not code |
unsupported_grant_type | grant_type is not authorization_code or refresh_token |
access_denied | The user declined on the consent screen |
Rate limiting
/oauth/register, /oauth/authorize, /oauth/token and /oauth/revoke are rate-limited per IP address, independently of the plan-tier limits described in Rate Limits. Back off on 429 and honour Retry-After.
Related
- For AI Agents — which auth method to pick, and the rest of the agent surface
- MCP Server — the hosted server that uses this flow
- Connected Apps — the user-facing revocation screen
- API Tokens — the simpler option for single-account access