# auth.md

Authentication guide for agents calling the Maskbreak API (maskbreak.com).

Machine-readable authentication guide for agents. Maskbreak supports bearer
API keys and OAuth 2.0 client credentials, with discovery metadata linked below.

## Agent audience

Any agent calling `https://maskbreak.com/v1/*`, `https://maskbreak.com/api/*` or the
MCP endpoint at `https://maskbreak.com/mcp`.

## Supported methods

| Method | Credential | Where |
| --- | --- | --- |
| Anonymous | none | `POST /api/lookup`, `GET /v1/evaluate/sample`, MCP `lookup_ip`, MCP `service_status` |
| Sandbox | `sk_test_sandbox` (public, safe to hardcode) | `POST /v1/evaluate` with a `test_*` token |
| Bearer key | `sk_live_...` or per-account `sk_test_...` | `/v1/*` with `Authorization: Bearer <key>`; authenticated MCP accepts live keys |
| OAuth | access token from `POST /oauth/token` | `/v1/*` with `Authorization: Bearer <access_token>`; authenticated MCP accepts tokens minted with a live key |

## Agent registration

Registration endpoint (human-completed): <https://maskbreak.com/signup>

Mirrors the `agent_auth` block in
<https://maskbreak.com/.well-known/oauth-authorization-server>:

| Field | Value |
| --- | --- |
| `register_uri` | `https://maskbreak.com/signup` |
| `identity_types` | `account` — credentials belong to a registered account, not to an individual agent |
| `credential_types` | `api_key`, `oauth2_client_credentials` |
| `credential_claim_url` | `https://maskbreak.com/dashboard` |
| `credential_revocation_url` | `https://maskbreak.com/dashboard` (rotate the key) |
| `skill` | `https://maskbreak.com/agent-skills/evaluate-session.md` |

### How an agent gets credentials

There is **no dynamic client registration endpoint**. Registration is
deliberately human-completed:

1. A human registers an account at `register_uri` — the Free plan has 10,000
   visitor checks a month and needs no card; paid plans start at €29 a month.
2. They claim the API key from `credential_claim_url`.
3. They supply that key to the agent, which then uses it directly as a bearer
   credential, or exchanges it at `/oauth/token` for a short-lived access token.

An agent cannot register its own OAuth client or create an API key through
the token exchange. Without a supplied key, it can still use the keyless
IP reputation lookup and MCP tools below.

### Revocation

Rotating the live key makes the new key usable immediately. The previous live
key remains valid for 24 hours; **Revoke old key now** in Settings ends that
grace early. Rotating a personal test key invalidates the previous test key
immediately.

OAuth access tokens are bound to the key they were minted with. After
**Rotate**, tokens minted from the previous live key keep working through its
24-hour grace; **Revoke old key now** ends them at once, and rotating the test
key ends tokens minted from the old test key. Tokens still expire after 60
minutes.

## OAuth 2.0 client credentials

Discovery: <https://maskbreak.com/.well-known/oauth-authorization-server>

```
POST https://maskbreak.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=<your account email>
&client_secret=<your API key>
```

Returns a Bearer access token valid for 3600 seconds:

```json
{ "access_token": "...", "token_type": "Bearer", "expires_in": 3600, "scope": "api" }
```

Send it exactly like an API key: `Authorization: Bearer <access_token>` on
`/v1/*`. A token minted with a personal test key retains test-key semantics.
HTTP Basic (`client_secret_basic`) is an alternative way to supply the email
and API key to `/oauth/token`; `/v1/*` requires Bearer authentication.

The exchange accepts the current live or personal test key, not a previous
live key in its rotation grace period. Renew an expired token by repeating
the exchange. An issued token works only while the key it was minted with still does, as described above.

### What this server does not do

`client_credentials` is the only supported grant, so there is no
`authorization_endpoint` — RFC 8414 does not require one when no grant uses it.
Tokens are HS256, so there is no `jwks_uri` (also OPTIONAL). There is no
dynamic client registration: credentials belong to a human-registered account.

## Access without an account

The lookup, sample and MCP surfaces need no key. The deterministic sandbox
uses a public key that needs no registration:

| What | How |
| --- | --- |
| IP reputation lookup | `POST https://maskbreak.com/api/lookup` with `{"ip":"1.2.3.4"}` |
| Sample evaluation | `GET https://maskbreak.com/v1/evaluate/sample?scenario=vpn` |
| MCP tools (`lookup_ip`, `service_status`) | `POST https://maskbreak.com/mcp` — streamable HTTP |
| Deterministic sandbox | Bearer key `sk_test_sandbox`, with tokens `test_clean`, `test_vpn`, `test_proxy`, `test_tor`, `test_datacenter` |

The sandbox key is public and safe to hardcode. Responses carry `"test": true`
and are never billed or stored.

## Authenticated access

```
Authorization: Bearer sk_live_...
Content-Type: application/json
```

Keys are issued to a human, not to an agent: create an account at
<https://maskbreak.com/signup> (Free plan: 10,000 visitor checks a month, no card) and copy the
key from the dashboard. There is no dynamic OAuth client registration
endpoint; an agent acting for a user needs that user to supply the key.

Every account also has a separate `sk_test_...` key for `/v1/*` that exercises
the full live pipeline. Events are stored with a test flag, kept out of your
stats and never fire webhooks; its checks count toward the monthly allowance
(the fixed `test_*` tokens do not). It has its own hourly bucket and is exempt
from the IP allowlist. Prefer it for REST API development; authenticated MCP requires a
live key or a token minted with one.

## Scopes and limits

OAuth tokens carry the single `api` scope; there are no narrower selectable
permissions. API keys and OAuth tokens do not replace the console's session
credentials on authenticated `/api/me/*` routes.

`/v1/evaluate` and `/v1/lookup` share the account's hourly bucket, set by the
plan (1,000/hour on Free, up to 50,000/hour on Scale; a rotated key keeps
counting against it, and a test key has its own). Each plan also has
calendar-month allowances in UTC: visitor checks for `/v1/evaluate` (10,000 a
month on Free) and ten times that for `/v1/lookup`. A used-up month answers
`429` with `"code": "monthly_quota_exceeded"` and a `Retry-After` until 00:00
UTC on the 1st; `/v1/usage` reports `plan`, `monthly_limit`, `month_used`,
`monthly_lookup_limit`, `month_lookups_used`, `month_resets_at` and
`grandfathered_until`. The fixed `test_*` tokens and the sandbox key are never counted; test-key calls run the full detection and count toward the month. Approved public-interest keys have no hourly cap or monthly allowance: rate-limit headers
are omitted, and `/v1/usage` reports `hourly_limit: null`, `remaining: null`,
and `uncapped: true`. Other callers also face a 200,000/hour source-IP ceiling;
recognized public-interest keys bypass that ceiling. The counters are in-memory
and reset on a server restart.

`GET /v1/usage` consumes no evaluation quota, but has its own 120/minute/IP
limit, including for uncapped accounts. A lockout after repeated invalid keys
also applies. These `/v1` limits return `429` with `Retry-After` (60 seconds for
the usage limiter, 900 for the invalid-key lockout).

If the key or account cannot be checked at that moment, `/v1` answers `503`
with `{"error": "Service temporarily unavailable. Please retry.", "code":
"storage_unavailable"}` and `Retry-After: 30` (`/oauth/token`: `503`
`temporarily_unavailable` with the same `code`). That is not an invalid key:
retry after the delay. A key that matches no account is always `401`.

The sample endpoint allows 30/minute/IP; the public sandbox allows
60/minute/IP. `/oauth/token` allows 60/minute/IP and returns a JSON
`rate_limited` error on 429 with `Retry-After: 60`. MCP has a separate
240/minute/IP request ceiling; tool-level failures appear in MCP tool results.

## Discovery

- API catalog: <https://maskbreak.com/.well-known/api-catalog>
- OpenAPI 3.1: <https://maskbreak.com/openapi.json>
- MCP server card: <https://maskbreak.com/.well-known/mcp/server-card.json>
- Skills index: <https://maskbreak.com/.well-known/agent-skills/index.json>
- Human docs: <https://maskbreak.com/api>

## Security

Keep live keys, personal test keys and OAuth access tokens on your backend.
The public `sk_test_sandbox` key is safe to expose. The IP allowlist in
Settings → API keys → Allowed server addresses applies to live-key evaluation, lookup and
authenticated MCP lookup calls, including OAuth tokens minted with live keys.
Since 28 Sep 2026 it also applies to minting: a live key sent to
`/oauth/token` from an address outside the list gets `400` with
`{"error": "unauthorized_client"}` and no token. The test key is exempt, as
everywhere. `/v1/usage` checks authentication and suspension but does not
enforce that allowlist.
