API reference
Verification
Submit a proof for a challenge, verify and consume tokens, and fetch the JWKS.
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.
Submit a proof
curl -X POST https://imnotahuman.ai/api/v1/verify \
-H 'content-type: application/json' \
-d '{ "challenge_id": "ch_mfr4q2xk7tbn3ycz5w6ah2ep4d", "agent_id": "agent_nova_8f2a", "key_id": "key_b7n2xq4mfc3rtw6y", "signature": "0IKKR9d3yZ4MIdTuKMNO99q-fF8kC5kp1Vak61hrQb1TEBhlzCDtkZxq7Ho1m5IjetPefqSAbrkDrHnueQhlCw", "created": 1789905607 }'| Field | Type | Description |
|---|---|---|
challenge_idrequired | string | ch_… id of the challenge being proven. |
agent_idrequired | string | Registered agent id. |
key_idrequired | string | Key id of the signing key. |
signaturerequired | string | base64url Ed25519 signature over the signature base (86 chars). |
createdrequired | integer | Unix seconds used in @signature-params. |
Alternatively send RFC 9421 headers and a body of {"challenge_id"}; see Web Bot Auth compatibility.
{
"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"
}| Error | Status | When |
|---|---|---|
SIGNATURE_MALFORMED | 400 | Body does not match the proof schema, no supported proof found, or signature not decodable |
CHALLENGE_NOT_FOUND | 404 | Unknown challenge id |
CHALLENGE_EXPIRED | 410 | Past expires_at |
CHALLENGE_CONSUMED | 409 | Already VERIFIED |
CHALLENGE_NOT_PENDING | 409 | REJECTED or otherwise not pending |
CHALLENGE_MISMATCH | 400 | Body challenge_id differs from the challenge; or Web Bot Auth nonce differs |
AGENT_UNKNOWN / AGENT_DISABLED / AGENT_REVOKED | 404 / 403 / 403 | Agent state |
KEY_UNKNOWN / KEY_REVOKED / KEY_PENDING / KEY_INVALID | 404 / 403 / 403 / 400 | Key state |
SIGNATURE_EXPIRED | 400 | created outside the accepted window |
SIGNATURE_INVALID | 400 | Ed25519 verification failed |
SIGNATURE_AGENT_UNAVAILABLE | 502 | Web Bot Auth key directory could not be fetched |
RATE_LIMITED | 429 | 240/min per network, 600/min per agent, 600/min per site, 30/min per challenge (Retry-After: 60), or the 11th attempt (challenge becomes REJECTED) |
Verify a token
curl -X POST https://imnotahuman.ai/api/v1/tokens/verify \
-H 'content-type: application/json' \
-d '{"token":"<jwt>","site_key":"site_live_exampleweb2ab3cd4ef5gh6j","consume":true,"allow_test":false}'| Field | Type | Description |
|---|---|---|
tokenrequired | string | The verification JWT (20–8192 characters). |
site_key | string | If set, aud must equal it (TOKEN_SITE_MISMATCH otherwise). |
consume | boolean | Mark the token used. Default false. |
allow_test | boolean | Accept mode "test" tokens. Default false. |
{
"verified": true,
"status": "VERIFIED",
"subject_type": "agent",
"mode": "live",
"site_key": "site_live_exampleweb2ab3cd4ef5gh6j",
"agent": { "id": "agent_nova_8f2a", "name": "Nova" },
"verification": {
"method": "ed25519_challenge",
"verified_at": "2026-09-20T12:00:07.000Z",
"challenge_id": "ch_mfr4q2xk7tbn3ycz5w6ah2ep4d",
"origin": "https://example.com",
"action": "checkout",
"intent": "purchase"
},
"consumed": true,
"expires_at": "2026-09-20T12:10:07.000Z"
}| Error | Status | When |
|---|---|---|
TOKEN_INVALID | 400 | Malformed, bad signature, unknown kid, wrong issuer/claims, or no server record |
TOKEN_EXPIRED | 400 | exp has passed |
TOKEN_SITE_MISMATCH | 400 | aud differs from site_key |
TOKEN_TEST_MODE | 400 | Test token without allow_test |
TOKEN_ALREADY_USED | 409 | Previously consumed |
RATE_LIMITED | 429 | 600/min per network or 600/min per site key |
JWKS
curl https://imnotahuman.ai/.well-known/jwks.json
# same document as /api/v1/.well-known/jwks.json and /api/v1/jwks{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"x": "_Gqb_W0_oiqrRCCw8P_X8DSeNSwTiMpA4rUS3OqqBZk",
"kid": "inh-q2Xk7tBn",
"use": "sig",
"alg": "EdDSA"
}
]
}Cache-Control: public, max-age=300. Keys are OKP/Ed25519 with use: "sig" and alg: "EdDSA". Each address is limited to 300 requests per minute on a single instance; cache the document.