Protocol
Tokens
Verification tokens are EdDSA JWTs scoped to a site key; their header, claims, lifetime, single-use semantics and the JWKS endpoint.
Format
A compact JWS with header {"alg":"EdDSA","typ":"JWT","kid":"inh-…"}. Only EdDSA is ever issued or accepted; alg: none and other families are rejected before any key lookup. Maximum length 8192 characters.
{
"iss": "https://imnotahuman.ai",
"sub": "agent_nova_8f2a",
"aud": "site_live_exampleweb2ab3cd4ef5gh6j",
"iat": 1789905607,
"exp": 1789906207,
"jti": "ch_mfr4q2xk7tbn3ycz5w6ah2ep4d",
"inh": {
"v": 1,
"mode": "live",
"challenge_id": "ch_mfr4q2xk7tbn3ycz5w6ah2ep4d",
"method": "ed25519_challenge",
"origin": "https://example.com",
"action": "checkout",
"intent": "purchase",
"agent": { "id": "agent_nova_8f2a", "name": "Nova" },
"subject_type": "agent"
}
}Claims
| Claim | Value | Description |
|---|---|---|
iss | https://imnotahuman.ai | Issuer. Must match exactly. |
sub | agent id | The verified agent: a registry id, agent_simulated_test, or the Web Bot Auth origin. |
aud | site key | The site key that created the challenge. Compare to your own key. |
iat | integer | Issued at (unix seconds) = verification time. |
exp | integer | iat + 600. |
jti | challenge id | Unique per token; equals the challenge id. Use it for replay tracking when verifying offline. |
inh.v | 1 | Claim-set version. |
inh.mode | "live" | "test" | Test tokens are rejected by tokens/verify unless allow_test is true. |
inh.challenge_id | string | Same as jti. |
inh.method | VerificationMethod | ed25519_challenge, web_bot_auth or simulated. |
inh.origin | string | Origin the challenge was created from. |
inh.action / inh.intent | string | As declared by the host when creating the challenge (empty strings when unset). |
inh.agent | { id, name } | Display information for the agent. |
inh.subject_type | "agent" | Fixed in v1. |
Single use
Each issued token is stored server-side keyed by jti. POST /api/v1/tokens/verify with consume: true marks it used atomically; any later presentation fails with TOKEN_ALREADY_USED. Without consume the endpoint only reports whether the token is still unconsumed. Offline verification cannot see consumption state.
JWKS
GET https://imnotahuman.ai/.well-known/jwks.json (also /api/v1/.well-known/jwks.json and /api/v1/jwks) returns the active signing keys with Cache-Control: public, max-age=300. Select the key by the token header's kid. Retired keys are removed from the set; cache no longer than a few minutes so rotation is picked up.
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"x": "_Gqb_W0_oiqrRCCw8P_X8DSeNSwTiMpA4rUS3OqqBZk",
"kid": "inh-q2Xk7tBn",
"use": "sig",
"alg": "EdDSA"
}
]
}Verification checklist
- Parse the compact JWS; require
alg = EdDSAand akid. - Verify the Ed25519 signature with the JWK whose
kidmatches. - Check
iss,exp > now,inh.v = 1. - Check
audequals your site key. - Reject
inh.mode = "test"in production. - Enforce single use: consume online, or track
jtiuntilexp. - Compare
inh.action/inh.intent/inh.originto the operation at hand.