Protocol
Challenge format
Every field of the challenge document and its public view, with types, formats and which fields are covered by the signature.
Example
GET /api/v1/challenges/:id
{
"challenge_id": "ch_mfr4q2xk7tbn3ycz5w6ah2ep4d",
"nonce": "aW1ub3RhaHVtYW4tZG9jcy1leGFtcGxlLW5vbmNlISE",
"site_key": "site_live_exampleweb2ab3cd4ef5gh6j",
"origin": "https://example.com",
"issued_at": 1789905600,
"expires_at": 1789905720,
"action": "checkout",
"intent": "purchase",
"mode": "live",
"status": "PENDING",
"purpose": "verification",
"challenge_url": "https://imnotahuman.ai/c/ch_mfr4q2xk7tbn3ycz5w6ah2ep4d",
"verify_url": "https://imnotahuman.ai/api/v1/verify"
}Fields
The first nine fields form the ChallengeDocument: the exact set bound into the signature. The rest are metadata.
| Field | Type | Description |
|---|---|---|
challenge_idrequired | string | ch_ + 26 base32 characters (a–z, 2–7): 128 bits of server-generated entropy. Unguessable; treat as a capability handle. |
noncerequired | string | base64url of 32 random bytes, generated per challenge. Public; it provides freshness, not secrecy. |
site_keyrequired | string | The site key that created the challenge (site_live_… or site_test_…). |
originrequired | string | Normalised Origin header of the creating request, e.g. https://example.com. No path, port only if present in the allowed origin. |
issued_atrequired | integer | Unix seconds. |
expires_atrequired | integer | Unix seconds; issued_at + ttl (default 120 s). |
actionrequired | string | Host-declared action, ≤ 64 characters of [A-Za-z0-9_.:-]. Empty string when unset. |
intentrequired | string | Host-declared intent, same constraints. Empty string when unset. |
moderequired | "live" | "test" | Derived from the site key prefix. |
status | ChallengeStatus | PENDING, VERIFIED, REJECTED, EXPIRED (REVOKED and ERROR are reserved). |
purpose | "verification" | "registration" | Registration challenges are bound to one pending key and issue no token. |
challenge_url | string | https://imnotahuman.ai/c/<challenge_id>: the canonical URL an agent can be pointed at. |
verify_url | string | https://imnotahuman.ai/api/v1/verify: where to POST the proof. |
result | VerificationResult | Present only when status is VERIFIED. |
Identifier formats
| Identifier | Pattern | Example |
|---|---|---|
| Challenge | ch_[a-z2-7]{26} | ch_mfr4q2xk7tbn3ycz5w6ah2ep4d |
| Agent | agent_[a-z0-9]{1,24}_[a-z0-9]{4,8} | agent_nova_8f2a |
| Key | key_[a-z2-7]{16} | key_b7n2xq4mfc3rtw6y |
| Site | site_[a-z2-7]{16} | site_c4n7xq2mfb3ktw6y |
| Site key | site_(live|test)_[a-z2-7]{20,26} | site_live_exampleweb2ab3cd4ef5gh6j |
| Registration token | reg_[a-z2-7]{32} | reg_… |
| Event | ev_[a-z2-7]{20} | ev_… |
Discovery on a page
The widget publishes the challenge document in a <script type="application/json" data-imnotahuman-challenge> element inside its host, and sets data-challenge-id and data-challenge-url on the host. An agent operating a browser can read either and then follow the API flow.