# Authentication

URL: https://igregulator.io/docs/authentication/
Markdown: https://igregulator.io/docs/authentication.md

> Create, use, rotate and revoke iGregulator API keys: Bearer tokens, the free Starter plan, endpoints that work without a key, and 401, 402 and 429 responses.

iGregulator uses **Bearer tokens**: a single opaque API key sent in the
`Authorization` header. No JWTs, no per-request signatures. Every
authenticated endpoint on `api.igregulator.io` uses the same scheme.

The one exception is the MCP server's **signed-in endpoint**,
`https://mcp.igregulator.io/mcp/account`: connectors in claude.ai, ChatGPT and
Claude Code sign in with your iGregulator account there (OAuth 2.1) instead of
taking a key. See [MCP server → Connect from claude.ai, ChatGPT or Claude Code](https://igregulator.io/docs/mcp/#connect-from-claudeai-chatgpt-or-claude-code-sign-in--no-key).

## 1. Overview

| | Public | Authenticated |
| --- | --- | --- |
| Needs a key | — | ✓ |
| Rate limit | 10 req / IP / hour | per-plan quota (Starter 10k/mo, Pro 100k/mo, Business fair-use) |
| Example endpoints | `/v1/check`, `/v1/jurisdictions`, `/v1/operators/search` | `/v1/operators/:slug`, `/v1/licenses/:id`, `/v1/jurisdictions/:code` |
| Who it's for | quick lookup, demo, embed in a landing page | production integrations, bulk jobs, compliance sweeps |

See [pricing](https://igregulator.io/#pricing) for the full plan
comparison. Signup is open and free for founding members (full Starter
plan); create an account at
[app.igregulator.io/signup](https://app.igregulator.io/signup). Paid
plans will be billed by card; they aren't open yet.

## 2. Generating keys

1. Sign in at [app.igregulator.io](https://app.igregulator.io/login).
2. Go to **[API keys](https://app.igregulator.io/api-keys)** in the nav.
3. Click **+ generate new key**. Give it a descriptive label
   ("Production server", "Local dev", "Staging job").
4. The raw key is displayed **once** — copy it now. We store a SHA-256
   hash only and cannot recover the plaintext. Lose it → rotate.

Key format: `igk_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` (36 chars total).

## 3. Using keys

**curl**

```bash
curl -H "Authorization: Bearer igk_yourkeyhere" \
  https://api.igregulator.io/v1/operators/paddy-power-holdings-limited
```

**JavaScript**

```js
const res = await fetch(
  'https://api.igregulator.io/v1/operators/paddy-power-holdings-limited',
  { headers: { Authorization: `Bearer ${process.env.IGREGULATOR_API_KEY}` } },
);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const operator = await res.json();
```

**Python**

```python
import os, requests

r = requests.get(
    'https://api.igregulator.io/v1/operators/paddy-power-holdings-limited',
    headers={'Authorization': f'Bearer {os.environ["IGREGULATOR_API_KEY"]}'},
    timeout=10,
)
r.raise_for_status()
operator = r.json()
```

Public endpoints work without a key too, but attaching one **skips the
10/hour IP cap** and uses your plan quota instead — useful when serving
dashboards that can burst past the public ceiling.

## 4. Key rotation

Rotation is overlap-based — create the new key, deploy it, then revoke
the old one. No grace period is needed at our end; old keys stay
valid until you explicitly revoke them.

1. Generate a new key. Label with the rotation reason.
2. Deploy the new key to every consumer (CI variables, running services,
   teammates' `.env` files).
3. Verify traffic shifted — check the *Last used* column on the
   [API keys](https://app.igregulator.io/api-keys) page; the old key
   should show no recent usage.
4. Click **Revoke** on the old key in the dashboard. Confirmation is
   required. Revocation is immediate — the next request carrying the
   old key returns `401 auth_revoked`.

> **If a key is compromised**
>
> Don't wait to rotate. Revoke immediately, generate a replacement, then
> investigate the leak — the damage window closes at revocation, not at
> replacement.

## 5. Security

- **Never commit keys to version control.** We don't scan public repos
  for you, and don't count on a secret scanner to catch an `igk_`
  key; a leaked key is your risk.
- **Use environment variables.** `process.env.IGREGULATOR_API_KEY` in
  Node, `os.environ['IGREGULATOR_API_KEY']` in Python, Docker secrets
  in containerised deploys.
- **One key per client.** Separate keys per environment (prod / staging /
  dev / CI) make revocation surgical — you kill the leaked instance
  without affecting every consumer.
- **Keys are stored hashed.** SHA-256, never plaintext. If the DB is
  ever read out, the keys themselves don't leak — only their prefixes
  (displayed in the UI anyway).
- **HTTPS only.** The API doesn't listen on port 80; HTTP would leak
  the key in plain text.
- **Report compromises** to founder@igregulator.io. We'll help
  triage and can check for anomalous usage patterns on our side.

## 6. Rate limits and quotas

Two independent ceilings enforced on every authenticated request, both
counted per account (all your keys share them):

- **Per-second rate limit** — your plan's ceiling
  (Starter 5/s, Pro 20/s, Business 100/s, Enterprise unlimited).
  Breach → `429 rate_limited` with `Retry-After: 1`.
- **Monthly request quota** — plan quota per calendar month, UTC
  reset at the first of the month. Breach → `429 quota_exceeded`.

Every successful authenticated response carries headers you can read to
stay ahead of the monthly quota:

| Header | Meaning |
| --- | --- |
| `X-Monthly-Quota-Limit` | Your plan's monthly ceiling, or `unlimited` |
| `X-Monthly-Quota-Used` | Count so far this month (omitted when `unlimited`) |
| `X-Monthly-Quota-Remaining` | Quota minus used (omitted when `unlimited`) |
| `X-Monthly-Quota-Reset` | ISO-8601 timestamp when the counter rolls over (omitted when `unlimited`) |
| `X-Monthly-Quota-Warning` | Present when usage ≥ 80%: `80% of monthly limit used` |
| `X-RateLimit-Limit` | The same **monthly** ceiling (not the per-second one). Omitted when `unlimited`. |
| `X-RateLimit-Remaining` | Monthly calls left. Omitted when `unlimited`. |
| `X-RateLimit-Reset` | Unix epoch seconds of the monthly reset. Omitted when `unlimited`. |
| `X-RateLimit-Policy` | Only `tier=unlimited` (plans with no monthly cap), or `tier=authenticated` when the key is used on a public endpoint. Otherwise absent. |
| `X-Upgrade-URL` | `https://igregulator.io/pricing` |

The per-second limit has no header on a successful response; a per-second
429 reports it (`X-RateLimit-Limit` = the per-second limit,
`X-RateLimit-Reset` = the next second). `RateLimit-Policy` and the
`tier=public;…` policy string are sent on keyless calls only. See the
[rate limits guide](https://igregulator.io/docs/rate-limits/) for the full tier table and every
kind of 429.

## 7. Errors

Authenticated endpoints return structured JSON on every non-2xx. Branch
on `code` for behaviour, `details.reason` for refinement, and use
`details.suggestion` verbatim in user-facing messaging when present.

| Status | code | When |
| --- | --- | --- |
| 401 | `auth_required` | No `Authorization` header. |
| 401 | `auth_invalid` | Header malformed or key not recognised. |
| 401 | `auth_revoked` | Key was revoked via the dashboard. |
| 402 | `payment_required` | `details.reason: plan_inactive` — the account has no plan or a `canceled` one. `endpoint_requires_paid_plan` — a legacy `trial` key on anything but `GET /v1/check` and the endpoints that work without a key. |
| 429 | `rate_limited` | Per-second ceiling breached (`details.reason: per_second_limit_exceeded`). Sleep 1 s, retry once. |
| 429 | `quota_exceeded` | Monthly quota exhausted (`details.reason: monthly_request_limit_reached`). Wait for `details.reset_at`, or email founder@igregulator.io. |

Full code reference lives in the [error handling guide](https://igregulator.io/docs/errors/).

Example body (401):

```json
{
  "error": "API key has been revoked",
  "code": "auth_revoked",
  "details": {
    "reason": "api_key_revoked",
    "suggestion": "Generate a new API key at https://app.igregulator.io/api-keys. Revoked keys cannot be restored."
  }
}
```
