# RankZero authentication for agents

RankZero has two protected surfaces, and each takes its own credential:

| Surface | Endpoint | Credential | Who it is for |
| --- | --- | --- | --- |
| MCP server | `https://www.rankzero.io/mcp` (Streamable HTTP) | OAuth 2.1 access token | An assistant acting for a signed-in RankZero user |
| REST API | `https://www.rankzero.io/api/v1` | Static bearer API key | Server-to-server integrations and reports |

An OAuth access token is not accepted by the REST API, and an API key is not accepted by the MCP server. Both only ever return brands the authenticated account owns or shares through its organization. There is no anonymous access and no sandbox.

## Discover

MCP server:

1. `POST https://www.rankzero.io/mcp` without credentials returns `401` with `WWW-Authenticate: Bearer error="invalid_token", resource_metadata="https://www.rankzero.io/.well-known/oauth-protected-resource/mcp"`.
2. `GET https://www.rankzero.io/.well-known/oauth-protected-resource/mcp` (RFC 9728 protected-resource metadata; the same document is at `https://www.rankzero.io/.well-known/oauth-protected-resource`). It names the authorization server `https://clerk.rankzero.io` and `scopes_supported`.
3. `GET https://www.rankzero.io/.well-known/oauth-authorization-server` (RFC 8414 authorization-server metadata) for the authorization, token, registration, and revocation endpoints.

REST API:

1. `GET https://www.rankzero.io/api/v1` without a key returns `401` with a JSON body and `WWW-Authenticate: Bearer realm="RankZero API", resource_metadata="https://www.rankzero.io/.well-known/oauth-protected-resource/api/v1"`.
2. `GET https://www.rankzero.io/.well-known/oauth-protected-resource/api/v1` describes the resource. It lists no authorization server, because keys are issued by RankZero directly, not through OAuth.
3. `GET https://www.rankzero.io/openapi.json` for the operations, parameters, and response schemas.

The authorization-server metadata carries no `agent_auth` block: RankZero does not run an `identity_endpoint`, `claim_endpoint`, or `events_endpoint`.

## Pick a method

| You are | Use |
| --- | --- |
| An MCP client (Claude, ChatGPT, an IDE) working with a person who has a RankZero account | OAuth 2.1 authorization code with PKCE, against the MCP server |
| A backend job, report generator, or integration owned by a RankZero account holder | A REST API key |

Agent identity types are not supported: no `identity_assertion` (including ID-JAG, `urn:ietf:params:oauth:token-type:id-jag`), no `service_auth`, and no `anonymous` registration. Do not send those requests; there is no endpoint to receive them.

## Register

- **OAuth (MCP):** register a client with RFC 7591 dynamic client registration at `https://clerk.rankzero.io/oauth/register`. Public clients use `token_endpoint_auth_method: "none"` with PKCE (`S256`).
- **API key (REST):** there is no self-serve registration. The account holder emails support@rankzero.io from the email address on their RankZero account and asks for a key. The key is bound to that account.

## Claim

- **OAuth (MCP):** send the user to the `authorization_endpoint` (`https://clerk.rankzero.io/oauth/authorize`) with `response_type=code`, your `code_challenge`, and the scopes you need (`profile email`). They sign in to RankZero and approve the connection. The account needs at least one brand for the tools to return anything.
- **API key (REST):** nothing to claim. RankZero issues the key to the account holder, who hands it to the integration.

## Exchange

- **OAuth (MCP):** `POST https://clerk.rankzero.io/oauth/token` with `grant_type=authorization_code`, the `code`, your `redirect_uri`, and the `code_verifier`. The authorization server also supports `grant_type=refresh_token`.
- **API key (REST):** nothing to exchange. The key is the bearer credential.

## Use the access_token

- **MCP:** send `Authorization: Bearer <access_token>` on every request to `https://www.rankzero.io/mcp`. Start with `list_brands`, then `set_active_brand`; the tool list is in the [server card](https://www.rankzero.io/.well-known/mcp/server-card.json).
- **REST:** send `Authorization: Bearer <api_key>` on every request under `https://www.rankzero.io/api/v1`. Start with `GET /brands` to learn the brand ids. Authenticated responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`; throttle on them.

## Errors

REST errors are JSON: `{"error": "<message>"}`.

| Status | Where | Meaning | What to do |
| --- | --- | --- | --- |
| `401` | MCP | Missing, expired, or invalid access token (`WWW-Authenticate` names the metadata) | Run the OAuth flow again or refresh the token |
| `401` | REST | `unauthorized`: missing or unknown API key | Check the `Authorization` header; ask support@rankzero.io if the key was revoked |
| `400` | REST | A query parameter or body failed validation (the message says which) | Fix the request; see the [OpenAPI spec](https://www.rankzero.io/openapi.json) |
| `404` | REST | `unknown brand`, or a brand outside the key's account | Call `GET /brands` and use an id from it |
| `409` | REST | `ambiguous brand, use id`: a domain or name matched more than one brand | Use the brand's `id` |
| `429` | REST | `rate limited` | Wait for `Retry-After` seconds |
| `500` | REST | `internal error` | Retry later with backoff |

## Revocation

- **OAuth (MCP):** revoke a token with RFC 7009 at `https://clerk.rankzero.io/oauth/token/revoke`. Security event delivery (RFC 8935) is not offered.
- **API key (REST):** the account holder asks support@rankzero.io to revoke or rotate it. Revocation is handled by RankZero, not instant, and not available through the API.

## References

- [MCP server guide](https://www.rankzero.io/docs/claude-mcp)
- [REST API reference](https://www.rankzero.io/docs/api-reference)
- [llms.txt](https://www.rankzero.io/llms.txt)
