# 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=<email of the account that owns the key>
&client_secret=<your API key>
```

In an organization, the key the console shows belongs to the organization's
owner, so `client_id` is the owner's email, not a member's.

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 live detection. 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). Login protection, email and phone
intelligence and the account network do not run for it (they answer
`why: "test"`, and the `account` object is left out), and since 27 October
2026 it adds nothing to a device's `times_seen` or to your device links:
`device.times_seen`, `returning`, `first_seen`, `linked_accounts` and
`multi_account` are left out of its answers, as they are for the console's
own checks. It 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.

Each plan 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
rotated key keeps counting against them. No account has an hourly limit (since
29 October 2026): the rate-limit headers are not sent and `/v1/usage` reports
`hourly_limit: null`, `remaining: null` and `uncapped: true` for every key. An
account created before 1 October 2026 has no cap at all until 30 November 2026,
and a custom plan agreed with Maskbreak has none either. 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 never count toward the month (the sandbox key has its own limit of 60 a minute per address); test-key calls run the detection and count toward the month. Approved public-interest keys have no monthly allowance either:
`/v1/usage` reports `monthly_limit: null` and `monthly_lookup_limit: null`.
Other callers also face a 200,000/hour source-IP ceiling;
recognized public-interest keys bypass that ceiling. The hourly counters are
kept in memory and start again when a new server version takes over (at each
deploy); the monthly counts are stored and are not reset.

`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.
