> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heretic.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Challenge and usage endpoints

> Mint a verification ceremony from your backend, read its outcome, and read this month's usage against your plan.

The [challenge widget](/integration/challenge-widget) is the drop-in path. This is the backend-orchestrated path underneath it: your server mints a ceremony, hands the visitor its URL, and polls for the outcome. Heretic reports what it measured; where to place the challenge and how to act on the outcome is entirely your policy.

All three endpoints take your secret key as a bearer token.

## Mint a challenge

```http theme={"dark"}
POST https://heretic.tech/v1/challenge
Authorization: Bearer hrtc_sk_...
Content-Type: application/json
```

```json theme={"dark"}
{ "mode": "phone", "account_ref": "acct-77", "ttl_s": 600 }
```

| Field         | Meaning                                                                                                                                                          |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mode`        | `phone`: the URL opens on the device being verified. `desktop`: the page shows a rotating QR that hands off to a phone.                                          |
| `account_ref` | Your opaque account handle, up to 128 characters. Optional. It scopes device cardinality: a passkey already registered to another account cannot register again. |
| `ttl_s`       | 120 to 900 seconds. Default 600.                                                                                                                                 |

Unknown fields are rejected.

```json theme={"dark"}
{
  "api_version": "2",
  "challenge_id": "chg_0123456789abcdef0123456789abcdef",
  "url": "https://heretic.tech/challenge/<token>",
  "mode": "phone",
  "expires_at": "2026-09-01T12:10:00.000Z"
}
```

Send the visitor to `url`. The URL carries a page capability independent of the challenge id: knowing one grants nothing about the other.

## Read the outcome

```http theme={"dark"}
GET https://heretic.tech/v1/challenge/{challenge_id}
Authorization: Bearer hrtc_sk_...
```

```json theme={"dark"}
{
  "api_version": "2",
  "challenge_id": "chg_0123456789abcdef0123456789abcdef",
  "status": "passed",
  "step_reached": "completed",
  "findings": [],
  "mode": "phone",
  "created_at": "2026-09-01T12:00:00.000Z",
  "expires_at": "2026-09-01T12:10:00.000Z",
  "completed_at": "2026-09-01T12:01:12.000Z"
}
```

`status` is exactly `pending`, `passed`, `contradicted`, or `not_completed`. There is no "unsupported" state and no fallback route. A device that cannot or will not complete a step is `not_completed`, and `step_reached` names the step: `motion-permission` (motion access declined), `motion-idle` (no motion reported), `tilt-timeout`, `rotate-timeout`, `evidence-measured` (the measurement session could not be taken), and the passkey stops `bytes-issued`, `ceremony`, `ceremony-excluded` (credential already registered elsewhere), `assertion-received`. A challenge still `pending` at `expires_at` becomes `not_completed` on the next read.

`findings` carry stable `id` and `tier` values under the same algebra as verdict signals: an `absolute` or `composite` finding sets `contradicted` on its own; a `conditional` finding is reported and never conclusive. Treat prose fields as prose.

The ceremony holds the device to a signed measurement: the passkey challenge bytes are derived from the hash of the results the device submitted, so the signature covers the measurements. Results the signature does not cover are `ceremony.results-unsigned`, absolute. An assertion returned faster than a person can complete a passkey prompt is `ceremony.instant-assertion`, conditional.

## Status codes

| Code  | Meaning                                                                                                                                                     |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | Malformed create body.                                                                                                                                      |
| `401` | No or malformed bearer token.                                                                                                                               |
| `402` | Your plan has stopped at its monthly ceremony quota. Only Sandbox stops. Body: `{"error": "quota-exceeded", "plan": "sandbox", "used": 100, "limit": 100}`. |
| `404` | Unknown key, or a challenge that is not yours.                                                                                                              |
| `429` | Over budget: 600 calls a minute per address. `retry-after` is set.                                                                                          |
| `503` | Store failure. Never becomes `passed`.                                                                                                                      |

Challenges are incompatible with zero-data-retention sessions: the ceremony's evidence must persist for the outcome to be provable.

## Usage

```http theme={"dark"}
GET https://heretic.tech/v1/usage
Authorization: Bearer hrtc_sk_...
```

```json theme={"dark"}
{
  "api_version": "2",
  "plan": "signal",
  "period_start": "2026-09-01T00:00:00.000Z",
  "probes":     { "used": 10400, "limit": 10000, "overage": 400, "overage_usd": 1, "blocked": false },
  "challenges": { "used": 7,     "limit": 1000,  "overage": 0,   "overage_usd": 0, "blocked": false }
}
```

Counts are for the current UTC calendar month. A probe is counted when the sensor opens a session for one of your sites; a challenge when a ceremony is minted, by this API or by the widget. `limit` is `null` on an unmetered plan. `blocked` is true only on a plan that stops at its limit: until the month turns, the sensor opens no sessions for your sites and no ceremony is minted. See [pricing](/reference/pricing).


## Related topics

- [Challenge widget](/integration/challenge-widget.md)
- [Dashboard, sites, and keys](/dashboard/dashboard-and-keys.md)
- [Tenant verdict endpoint](/api/verdict-endpoint.md)
- [Pricing](/reference/pricing.md)
- [Collector reference](/integration/collector.md)
