API reference
Challenges
Create, read and (in test mode) simulate challenges. Public endpoints used by the widget and by agents.
Base URL https://imnotahuman.ai/api/v1. Request bodies must be JSON with Content-Type: application/json (else 415) and at most 32,000 bytes. Bodies are validated strictly: unknown fields fail with VALIDATION_FAILED. Every response carries X-Request-Id (echoing yours if you send one) and Cache-Control: no-store except JWKS. Errors are { "error": { "code", "message", "details"? } }; see Errors.
Create a challenge
curl -X POST https://imnotahuman.ai/api/v1/challenges \
-H 'origin: https://example.com' \
-H 'content-type: application/json' \
-d '{"site_key":"site_live_exampleweb2ab3cd4ef5gh6j","action":"checkout","intent":"purchase","ttl":120}'| Field | Type | Description |
|---|---|---|
site_keyrequired | string | Live or test site key. |
action | string | ≤ 64 characters of [A-Za-z0-9_.:-]. Bound into the signature and token. |
intent | string | Same constraints as action. |
ttl | integer | Challenge lifetime in seconds, 30–300. Default 120. |
{
"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"
}| Error | Status | When |
|---|---|---|
ORIGIN_REQUIRED | 400 | Origin header missing or not a valid origin |
SITE_KEY_INVALID | 400 | Unknown site key |
SITE_DISABLED | 403 | Site status is disabled |
ORIGIN_UNAUTHORIZED | 403 | Origin not allowed for the site |
VALIDATION_FAILED | 422 | Bad field values; details.issues lists them |
RATE_LIMITED | 429 | 240/min per network, 600/min per site, or 30 unknown keys/min; Retry-After: 60 |
Read a challenge
curl https://imnotahuman.ai/api/v1/challenges/ch_mfr4q2xk7tbn3ycz5w6ah2ep4dReturns the ChallengePublic document. When status is VERIFIED a result object is included; its token is present only while the token is unconsumed and unexpired. A PENDING challenge past expires_at is marked EXPIRED on read. Rate limits: 900/min per network, 240/min per challenge. Errors: CHALLENGE_NOT_FOUND (404) for unknown or malformed ids.
{
"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": "VERIFIED",
"purpose": "verification",
"challenge_url": "https://imnotahuman.ai/c/ch_mfr4q2xk7tbn3ycz5w6ah2ep4d",
"verify_url": "https://imnotahuman.ai/api/v1/verify",
"result": {
"verified": true,
"status": "VERIFIED",
"subject_type": "agent",
"mode": "live",
"agent": { "id": "agent_nova_8f2a", "name": "Nova" },
"verification": {
"method": "ed25519_challenge",
"verified_at": "2026-09-20T12:00:07.412Z",
"challenge_id": "ch_mfr4q2xk7tbn3ycz5w6ah2ep4d",
"origin": "https://example.com",
"action": "checkout",
"intent": "purchase"
},
"token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImluaC1...",
"expires_at": "2026-09-20T12:10:07.000Z"
}
}Simulate (test mode only)
curl -X POST https://imnotahuman.ai/api/v1/challenges/ch_.../simulate \
-H 'content-type: application/json' -d '{}'Completes a PENDING test-mode challenge as agent agent_simulated_test ("Simulated agent (test mode)") with method simulated and returns a VerificationResult including a test token. The body is optional. Rate limit 120/min per network.
| Error | Status | When |
|---|---|---|
TEST_MODE_REQUIRED | 400 | Challenge is live-mode or a registration challenge |
CHALLENGE_EXPIRED | 410 | Already expired |
CHALLENGE_NOT_PENDING | 409 | Already VERIFIED or REJECTED |
CHALLENGE_CONSUMED | 409 | Lost a race with another completion |
Health
curl https://imnotahuman.ai/api/v1/health
# {"status":"ok","version":"1.0.0","checks":{"database":"ok"}}Returns 200 with status: "ok", or 503 with status: "degraded" and checks.database: "unavailable". Monitors should call https://imnotahuman.ai/api/v1/health. The cap is 60 requests per minute per network, which covers a check every few seconds and still rejects a flood. Over the cap the response is 429 with Retry-After: 60.