# Authentication at Zerocost

> How agents authenticate to Zerocost: no credentials for public data, OAuth 2.0 (authorization code + PKCE) for a user's own account data, and API keys for the SDK.

## 1. Discover

Start from any protected endpoint or the metadata documents:

- Call a protected endpoint without a token, e.g. `GET https://zerocost.app/api/v1` or an account tool on `POST https://zerocost.app/mcp`. You get `401` with `WWW-Authenticate: Bearer resource_metadata="..."`.
- Protected resource metadata (RFC 9728): https://zerocost.app/.well-known/oauth-protected-resource/api/v1 (REST) and https://zerocost.app/.well-known/oauth-protected-resource/mcp (MCP). Both list `authorization_servers: ["https://zerocost.app"]` and `scopes_supported`.
- Authorization server metadata (RFC 8414): https://zerocost.app/.well-known/oauth-authorization-server. It has the authorization, token and registration endpoints and the supported scopes, grant types and PKCE methods.

## 2. Pick a method

| You need | Method | Credential |
| --- | --- | --- |
| Docs, pricing, page index, NLWeb, docs MCP | None | - |
| A user's profile, apps, earnings or API keys | OAuth 2.0 authorization code + PKCE | Bearer access token |
| Testing an integration against account endpoints | Sandbox | `Authorization: Bearer zc_sandbox` |
| Serving ads and sending events from an app | SDK API key | `x-api-key` header |

Zerocost does not support agent self-registration: an agent cannot obtain credentials on its own, from an identity provider's assertion, or anonymously. Every token comes from a person approving access in the browser, so agents act for a user through standard OAuth with the user's consent.

## 3. Register

Register an OAuth client dynamically (RFC 7591):

```http
POST https://zerocost.app/oauth/register
Content-Type: application/json
Idempotency-Key: <unique key>

{"client_name": "My Agent", "redirect_uris": ["http://127.0.0.1:8765/callback"], "token_endpoint_auth_method": "none"}
```

- `redirect_uris`: https URLs, loopback http URLs (127.0.0.1, localhost, [::1]) or private-use schemes for native apps.
- `token_endpoint_auth_method`: `none` (public client, PKCE) or `client_secret_post` / `client_secret_basic` (you get a `client_secret`).
- With an `Idempotency-Key`, a retried registration returns the same `client_id`.
- Instead of registering, you can use a Client ID Metadata Document URL (https) as your `client_id`.

## 4. Claim (user consent)

There is no separate claim ceremony. Send the user's browser to the authorization endpoint:

```
https://zerocost.app/oauth/authorize?response_type=code&client_id=...&redirect_uri=...&scope=profile:read%20apps:read&state=...&code_challenge=...&code_challenge_method=S256
```

The user signs in to Zerocost (email and password or Google), sees your client name, the scopes and where they will be sent back, and approves or denies. You receive `code`, `state` and `iss` (or `error=access_denied`) on your redirect URI. PKCE with `S256` is required.

### Scopes

Request only what you need, space-separated in `scope`. Omitting `scope` requests all of them.

| Scope | Grants |
| --- | --- |
| `profile:read` | See your name, company, role and email. |
| `apps:read` | See your apps and ad placements, including impressions, clicks and revenue. |
| `earnings:read` | See your earnings totals and payout history. |
| `api_keys:read` | See the names, environments and status of your API keys (keys are masked). |

## 5. Exchange

```http
POST https://zerocost.app/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=...&redirect_uri=...&client_id=...&code_verifier=...
```

The response is `{"access_token": "...", "token_type": "Bearer", "expires_in": 3600, "scope": "..."}`. Codes are valid for 60 seconds. Access tokens last up to one hour and there are no refresh tokens: run the flow again when one expires.

## 6. Use the access_token

Send `Authorization: Bearer <access_token>`.

| Scope | REST | MCP tool (https://zerocost.app/mcp) |
| --- | --- | --- |
| `profile:read` | `GET /api/v1/me` | `whoami` |
| `apps:read` | `GET /api/v1/me/apps` | `list_my_apps` |
| `earnings:read` | `GET /api/v1/me/earnings` | `get_my_earnings` |
| `api_keys:read` | `GET /api/v1/me/api-keys` | `list_my_api_keys` |

Everything is read-only. No scope allows writes, payouts, campaign changes or reading unmasked API keys. Reads run as the user, so Zerocost's row-level security applies on top of the scopes.

### Sandbox

To build or test against the account API without a Zerocost account, send `Authorization: Bearer zc_sandbox` to any `/api/v1/me` endpoint, `POST /api/v1/batch`, or the account tools on `https://zerocost.app/mcp`. It grants every scope but only ever returns fixed demo data: responses include `"sandbox": true` (and a `Zerocost-Sandbox: true` header on REST). It never reads or changes a real account.

## 7. Errors

| Response | Meaning | What to do |
| --- | --- | --- |
| `401` + `WWW-Authenticate: Bearer resource_metadata="..."` | No token | Start at step 1 |
| `401` + `error="invalid_token"` | Token expired, malformed or from another deployment | Run the flow again |
| `403` + `error="insufficient_scope", scope="..."` | Token lacks a scope | Re-authorize with the listed scopes |
| `400 invalid_grant` at /oauth/token | Code expired, reused by another client, or PKCE verifier mismatch | Restart from step 4 |
| `400 invalid_scope` / `invalid_request` on the redirect | Bad authorization request | Fix the parameters |
| `429` + `Retry-After` | Rate limit hit | Wait and retry |
| `503 temporarily_unavailable` | OAuth not configured on this deployment | Retry later |

API errors are JSON: `{"error": {"status", "code", "message"}}`. OAuth endpoint errors use the RFC 6749 shape `{"error", "error_description"}`.

## 8. Revocation

There is no revocation endpoint. Access tokens cannot be revoked individually and expire within one hour; discard tokens you no longer need. Users stop future grants by not approving the consent screen.

## Rate limits

120 requests per 60 seconds per client IP on /api/v1, /mcp, /docs/mcp, /ask, /a2a, /oauth/register and /oauth/token. Responses carry `RateLimit-Policy` and `RateLimit` (draft-ietf-httpapi-ratelimit-headers) and `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`. Over the limit you get `429` with `Retry-After`. Limits are counted per edge instance, so treat them as a ceiling rather than a guarantee.

## Versioning and deprecation

Full policy: https://zerocost.app/developers/versioning

- The API version is in the URL path (/api/v1) and in the API-Version response header.
- Within v1, changes are additive only: new endpoints, new optional parameters and new response fields. Clients should ignore fields they do not recognise.
- Breaking changes ship as a new version (/api/v2). The previous version keeps working alongside it.
- Before a v1 endpoint or field is removed, its responses carry a Deprecation header (RFC 9745) and a Sunset header (RFC 8594) at least 90 days before the removal date.
- Deprecated responses also carry a Link header with rel="deprecation" pointing at migration notes.
- Deprecations are announced on this page, in /auth.md and in the OpenAPI spec (operations are marked deprecated: true).
- v1 is the current version. No endpoints are deprecated.

## SDK endpoints (API key)

The `@zerocost/sdk` SDK authenticates every request with an API key sent in the `x-api-key` header.

- Keys are self-serve: create a free account at https://zerocost.app/register, then manage keys at https://zerocost.app/startup/api-keys.
- Each key belongs to one app and carries an environment label, `development` or `production`, for your own bookkeeping. Both serve and record live traffic; there is no separate sandbox API yet. Preview ad formats without live traffic in the browser playground: https://zerocost.app/playground
- A key can be revoked from the dashboard at any time; revoked keys return 403.
- Check a key: `GET https://zerocost.app/functions/v1/validate-key` with header `x-api-key: <key>`.

Keys are publishable (they ship in client-side code) and only grant the SDK capabilities of the app they belong to: fetching ad placements, recording impressions and clicks, and submitting consented data. They cannot read earnings, change settings or access other apps.
