# Serql authentication for agents

This document follows the [auth.md](https://github.com/workos/auth.md) convention: a procedural recipe an agent can follow to obtain credentials without a human reading the docs. Serql uses standard **OAuth 2.0 authorization code with PKCE** issued by the deployment's identity provider (Clerk). There is no `agent_auth` identity-assertion extension: every token is bound to a real user who consents in a browser once.

## Discover

1. Send any request to the API without credentials, for example `GET https://app.serql.io/api`. You receive `401 Unauthorized` with:

   ```http
   WWW-Authenticate: Bearer resource_metadata="https://app.serql.io/.well-known/oauth-protected-resource"
   ```

2. Fetch the **protected resource metadata** (RFC 9728) at `https://app.serql.io/.well-known/oauth-protected-resource`. It lists `resource`, `authorization_servers`, `scopes_supported`, and `bearer_methods_supported` (`header`). The MCP endpoint has its own document at `https://www.serql.io/.well-known/oauth-protected-resource/mcp`.
3. Fetch the **authorization server metadata** (RFC 8414) at `https://app.serql.io/.well-known/oauth-authorization-server` (or the issuer's own `/.well-known/oauth-authorization-server` named in `authorization_servers`). It provides `authorization_endpoint`, `token_endpoint`, `revocation_endpoint`, `jwks_uri`, `code_challenge_methods_supported` (`S256`), and `grant_types_supported` (`authorization_code`, `refresh_token`).

## Pick a method

| Method | Who it is for | Notes |
| --- | --- | --- |
| **OAuth 2.0 authorization code + PKCE** (recommended) | MCP clients (Claude, ChatGPT, Cursor, custom agents) and integrations acting for a user | Public clients are supported (`token_endpoint_auth_methods_supported` includes `none`). |
| **Browser session** | Humans using the web app | Clerk session cookie; not usable by agents. |
| **Share-link token** | Read-only public dashboards | `GET https://app.serql.io/api/share/{token}/performance` needs no OAuth; the token is the URL segment of a share link a user created. |

API keys and client-credentials (machine-to-machine) grants are **not** offered. Anonymous access to Search Console data is never possible.

## Register

Obtain an OAuth client. MCP clients that support **Client ID Metadata Documents** (CIMD) or **Dynamic Client Registration** (RFC 7591) can register automatically when the deployment has enabled it in Clerk (the authorization server metadata advertises `registration_endpoint` or `client_id_metadata_document_supported` when it does). Otherwise ask the operator to create an OAuth application for you and provide a `client_id` and redirect URI (support@serql.io).

Requested scopes: `openid profile email` (add `offline_access` to receive a refresh token).

## Claim

No claim step is needed. The user proves ownership by signing in and consenting at the `authorization_endpoint`; the resulting token is bound to that user and inherits exactly the properties they own or were granted through team memberships.

## Exchange

1. Generate a PKCE `code_verifier` and `code_challenge` (S256).
2. Send the user to `{authorization_endpoint}?response_type=code&client_id=...&redirect_uri=...&scope=openid%20profile%20email&code_challenge=...&code_challenge_method=S256&state=...`.
3. Exchange the returned `code` at `{token_endpoint}` with `grant_type=authorization_code`, `code`, `redirect_uri`, `client_id`, and `code_verifier`. The response contains `access_token`, `token_type: Bearer`, `expires_in`, and optionally `refresh_token`.
4. Refresh with `grant_type=refresh_token` before `expires_in` elapses.

## Use the access_token

Send the token in the `Authorization` header to either surface:

```http
GET https://app.serql.io/api/properties?days=14 HTTP/1.1
Authorization: Bearer <access_token>
Accept: application/json
```

```http
POST https://www.serql.io/mcp HTTP/1.1
Authorization: Bearer <access_token>
Content-Type: application/json
Accept: application/json, text/event-stream
```

- The REST API is described at https://www.serql.io/openapi.json. Responses are JSON; rate-limit state is in `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `Retry-After`.
- The MCP server uses Streamable HTTP; tools and resources are listed in the server card at https://www.serql.io/.well-known/mcp/server-card.json.
- Every request is authorized per property: the token can only see properties the consenting user can see in the web app.

## Errors

| Status | Body | What to do |
| --- | --- | --- |
| `401` | `{ "error": "Unauthorized", "code": "unauthorized", "hint": ... }` plus `WWW-Authenticate` | Token missing, expired, or not a Clerk OAuth/session token. Refresh or restart the authorization flow. |
| `402` | `{ "error": ..., "code": "subscription_required" }` | The user's plan does not include access; the user must subscribe at `https://app.serql.io/subscribe`. |
| `403` | `{ "error": ... }` | Authenticated but the user's role on the property is too low for the action. |
| `404` | `{ "error": "Not found" }` | Property does not exist, is not tracked, or is not visible to this user (never distinguishes the three). |
| `429` | `{ "error": "Too many requests" }` with `Retry-After` | Back off for the indicated seconds. |
| `5xx` | `{ "error": "An unexpected error occurred.", "correlationId": "…" }` | Retry with backoff; quote the `correlationId` when contacting support. |

OAuth errors at the token endpoint follow RFC 6749 (`invalid_grant`, `invalid_client`, `invalid_request`).

## Revocation

- Agents can revoke a token at the `revocation_endpoint` from the authorization server metadata.
- Users can revoke an application's access from their account settings; subsequent requests receive `401`.
- Deleting the account (`POST https://app.serql.io/api/privacy/delete`) removes all data and invalidates every token.

## Related

- Developer docs: https://www.serql.io/docs
- MCP guide: https://www.serql.io/docs/mcp
- Contact: https://www.serql.io/contact
