# API Documentation

All endpoints return JSON and live under `/api`. State-changing requests require the CSRF token
(`X-CSRF-Token` header, value from the `<meta name="csrf-token">` on the page) and are rate-limited.

## `GET /api/challenge`

Issues a fresh verification session + challenge (used to retry without a full page reload).

**Response**
```json
{
  "token": "b3f1…",
  "provider": "puzzle",
  "challenge": { "type": "slider", "track_width": 280, "piece_size": 42, "target": 190, "tolerance": 10 },
  "issued_at_ms": 1731000000000
}
```

For remote providers the `challenge` object contains `{ "type": "turnstile", "site_key": "…" }` instead.

## `POST /api/verify`

Receives the client-collected signal bundle, scores it and returns the decision.

**Headers:** `Content-Type: application/json`, `X-CSRF-Token: <token>`, `X-Requested-With: XMLHttpRequest`

**Request body** (abridged — all fields optional except `token`; missing signals are scored conservatively):
```json
{
  "token": "b3f1…",
  "javascript": { "enabled": true, "es6": true, "async": true, "promise": true, "event_loop": true, "webdriver": false },
  "fingerprint": { "screen_width": 1920, "timezone": "America/New_York", "canvas_hash": "…",
                   "webgl_renderer": "ANGLE (NVIDIA…)", "hardware_concurrency": 8, "fonts": ["Arial"], "hash": "…" },
  "cookies": { "enabled": true, "persisted": true, "local_storage": true, "session_storage": true },
  "mouse": { "move_count": 180, "speed_stddev": 0.4, "straightness": 0.7, "click_interval_stddev": 220 },
  "keyboard": { "keydown_count": 0 },
  "scroll": { "event_count": 12, "speed_stddev": 0.3, "direction_changes": 3, "dwell_ms": 900 },
  "timing": { "time_on_page_ms": 7400, "time_to_interact_ms": 1200, "time_to_submit_ms": 5200 },
  "honeypot": { "contact_email": "", "website": "", "render_delay_ms": 6100 },
  "captcha": { "provider": "puzzle", "position": 190, "solve_time_ms": 2600, "retries": 0, "token": "" }
}
```

> Server-authoritative signals — `rate.*`, `session.*` and `captcha.verified` — are computed on the
> server and **override** any client-supplied values, so they cannot be spoofed by the client.

**Response**
```json
{
  "ok": true,
  "score": 92,
  "classification": "human",
  "action": "redirect_primary",
  "redirect_url": "https://your-site.com/app",
  "block_message": null,
  "breakdown": {
    "user_agent": { "score": 1.0, "weight": 15, "weighted": 15, "passed": ["…"], "failed": [] },
    "captcha":    { "score": 1.0, "weight": 20, "weighted": 20, "passed": ["Challenge verified"], "failed": [] }
  }
}
```

| `action` | Meaning |
|----------|---------|
| `redirect_primary` | Human — send to the configured primary URL |
| `redirect_alternate` | Suspicious — send to the alternate URL (or block page if unset) |
| `block` | Bot — show the block page + `block_message`; `redirect_url` is `null` |
| `allow` | Pass through without redirect |

**Errors**
- `422` — malformed payload (missing `token`)
- `419` — CSRF token mismatch
- `429` — rate limit exceeded (includes `Retry-After`)

---

## Authenticated REST API (v1)

Programmatic access for pulling your own verification data. Create a key under
**Dashboard → API Keys**; it is shown once. Authenticate with either header:

```
Authorization: Bearer bg_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-API-Key: bg_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Keys are stored only as a SHA-256 hash; data returned is scoped to the key's owner.

### `GET /api/v1/stats?days=30`

```json
{ "ok": true, "range": { "days": 30 },
  "summary": { "total": 1284, "humans": 1010, "suspicious": 190, "bots": 84,
               "avg_score": 78.4, "js_disabled": 12, "cookies_disabled": 30 } }
```

### `GET /api/v1/results?limit=50`

```json
{ "ok": true, "count": 50, "results": [
  { "id": 9001, "created_at": "2026-07-10 12:00:00", "ip": "203.0.113.9", "country": "US",
    "browser": "Chrome", "os": "Windows 10/11", "device_type": "desktop",
    "score": 92, "classification": "human", "decision": "redirect_primary" }
] }
```

**Errors:** `401` — missing / invalid / revoked key. `429` — rate limited.
