Skip to main content

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.

Which auth method do I need?
  • 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

ScopeGrants
analytics:readRead-only access to the approved site's analytics. This is the only resource scope.
offline_accessAlso 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 onlytoken_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"
}'
FieldTypeRequiredNotes
redirect_urisstring[]Yes1–10 URIs. The redirect_uri you send later must match one exactly.
client_namestringNoShown to the user on the consent screen. Defaults to MCP client.
token_endpoint_auth_methodstringNoOnly 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
ParameterRequiredNotes
client_idYesFrom step 1.
redirect_uriYesMust exactly match one of the registered URIs.
response_typeYesMust be code. Anything else returns unsupported_response_type.
scopeNoSpace-separated. Defaults to read-only if omitted.
stateNoStrongly recommended — your CSRF token, returned unchanged.
code_challengeYesS256 challenge derived from your verifier.
code_challenge_methodYesS256. plain is not supported.
resourceNoRFC 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.

Invalid client or redirect URI is never redirected

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:

EndpointMethodDescription
/oauth/connectionsGETList the user's active grants
/oauth/connections/{grant_id}DELETERevoke one grant
/oauth/consent/{request_id}GETDetails of a pending authorization request
/oauth/consent/{request_id}/decisionPOSTApprove or deny, and choose the site
/oauth/onboardingPOSTComplete 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"
}
errorMeaning
invalid_requestA required parameter is missing or malformed
invalid_clientUnknown client_id
invalid_redirect_uriThe redirect_uri does not match a registered URI
invalid_grantCode or refresh token is invalid, expired, already used, or the PKCE verifier does not match
invalid_scopeA requested scope is not in scopes_supported
unsupported_response_typeresponse_type is not code
unsupported_grant_typegrant_type is not authorization_code or refresh_token
access_deniedThe 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.

  • 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