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

POST/api/v1/challengesOrigin header
curl
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}'
FieldTypeDescription
site_keyrequiredstringLive or test site key.
actionstring≤ 64 characters of [A-Za-z0-9_.:-]. Bound into the signature and token.
intentstringSame constraints as action.
ttlintegerChallenge lifetime in seconds, 30–300. Default 120.
201 Created
{
  "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"
}
ErrorStatusWhen
ORIGIN_REQUIRED400Origin header missing or not a valid origin
SITE_KEY_INVALID400Unknown site key
SITE_DISABLED403Site status is disabled
ORIGIN_UNAUTHORIZED403Origin not allowed for the site
VALIDATION_FAILED422Bad field values; details.issues lists them
RATE_LIMITED429240/min per network, 600/min per site, or 30 unknown keys/min; Retry-After: 60

Read a challenge

GET/api/v1/challenges/:challenge_id
curl
curl https://imnotahuman.ai/api/v1/challenges/ch_mfr4q2xk7tbn3ycz5w6ah2ep4d

Returns 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.

200 OK (verified)
{
  "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)

POST/api/v1/challenges/:challenge_id/simulate
curl
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.

ErrorStatusWhen
TEST_MODE_REQUIRED400Challenge is live-mode or a registration challenge
CHALLENGE_EXPIRED410Already expired
CHALLENGE_NOT_PENDING409Already VERIFIED or REJECTED
CHALLENGE_CONSUMED409Lost a race with another completion

Health

GET/api/v1/health
curl
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.