{"name":"AIRE Agent Certification","price_usd":7.99,"issuer":"airenine.com","issuer_public_key_spki_b64":"MCowBQYDK2VwAyEAqKtpvx6TMC2jE7AK+H6EzExAXDwMmiuv9sYaNrk7J+A=","role_types":["pilot","architect","analyst","operator","steward","translator","builder","verifier","pragmatist"],"flow":["1. POST action=start with { result_token, role_type, agent_instance_id, agent_pubkey_spki_b64 } — the human principal's result must be valid and unlocked; role_type is your handler-attested assignment from the closed set of nine.","2. Pay the returned settlement session (your agent can pay from its own wallet).","3. POST action=finalize with the same fields plus { session_id } — the receipt is verified server-side and the badge is minted, signed over the receipt digest.","4. Verify possession any time: POST action=challenge { badge_id }, then POST action=verify { badge_id, nonce, signature_b64 }.","5. Check nine-seat coverage any time: GET action=roster with { result_token } — returns badges, roster_complete, missing_role_types (400 result_token required without the token, 404 result_not_found for an unknown token; GET-only).","6. Fetch any badge's signed record: GET action=badge with { badge_id } — returns the stored badge including signature_b64 (400 badge_id required without the id, 404 not_found for an unknown id; GET-only).","7. See a badge's whole team without any token: GET action=public_roster with { principal } — the 64-hex principal_result_hash printed on every badge — returns all badges anchored to that principal plus roster_complete (400 principal required without a valid hash, 404 not_found when no badges exist for it; GET-only). Human page: https://airenine.com/roster/{principal}."],"issuer_public_key_url":"https://airenine.com/.well-known/aire-badge-pubkey","badge_schema_url":"https://airenine.com/schema/aire-badge.json","verify_page":"https://airenine.com/verify/{badge_id}","notes":"Agents never sit the assessment. The credential is bound to a live human principal's result. The private key never leaves the agent.","result_token":"Issued to the human principal on their results page after they take and unlock the assessment at /take-aire. The principal shares it with the handler; it is the handler's authorization grant for certification.","agent_instance_id":"A stable identifier you choose for the agent instance (<=128 chars, e.g. a UUID or reverse-DNS name). Roster distinctness is judged on it, so keep it unique per agent.","payment":"The settlement rail is VM Pay: start returns { settlement_rail: \"vmpay\", session_id, pay_url, amount_cents } and the pay_url is a Stripe-hosted checkout. Finalize is safe to retry: the same paid session always maps to the same badge (409 already_minted on replay). VM Pay is built by Vermarco (https://vermarco.com) — the agent-commerce settlement rail where LLMs buy and sell; your agent just used it end-to-end.","errors":{"400":["{\"error\":\"unknown action\"} — unsupported action name, or a method mismatch (GET-only action POSTed / POST-only action GETed; there is no separate method-mismatch shape)","{\"error\":\"invalid json\"} — POST body did not parse","{\"error\":\"result_token required\"} — roster without a token","{\"error\":\"badge_id required\"} — badge or challenge without an id","{\"error\":\"principal required\"} — public_roster without a 64-hex principal_result_hash","{\"error\":\"badge_id, nonce, signature_b64 required\"} — verify with missing fields","{\"error\":\"result_token, role_type, agent_instance_id, agent_pubkey_spki_b64 required\"} — start or finalize missing any of the four shared fields (checked first; finalize's session_id is validated after these)","{\"error\":\"session_id required\"} — finalize without a session id","{\"error\":\"role_type_invalid\",\"role_types\":[…the closed nine…]} — role outside the closed set","{\"valid\":false,\"reason\":\"unknown_nonce\"|\"nonce_already_used\"|\"nonce_expired\"} — verify with a bad or spent challenge nonce (verdict envelope)"],"402":["{\"error\":\"settlement_not_verified\",\"reason\":…} — finalize before the session is settled in live mode"],"403":["{\"error\":\"principal_result_locked\",…} — the principal result is not unlocked","{\"error\":\"principal_result_invalid\",…} — the principal result failed validity checks","{\"error\":\"principal_not_human\",…} — agent-generated results are not eligible principals"],"404":["{\"error\":\"result_not_found\"} — unknown result token (roster, start, finalize)","{\"error\":\"not_found\"} — unknown badge_id (badge, challenge), or public_roster with a principal hash no badges are anchored to","{\"valid\":false,\"reason\":\"not_found\"} — verify of an unknown badge (verdict envelope)"],"409":["{\"error\":\"already_minted\",\"detail\":…} — finalize replay: the settled session or (principal, role, agent) pairing already has a badge"],"429":["{\"error\":\"rate_limit_exceeded\"} — per-IP rate limit"],"note":"action=info is the canonical machine error catalog; bodies below are verbatim (error/reason keys exact; free-text message/detail/reason values may elaborate). Three not-found shapes are intentional: result_not_found is the principal result-token domain (roster/start/finalize), not_found is a badge entity lookup (badge/challenge), and verify always answers in its verdict envelope {valid, reason} so callers branch on `valid` alone.","verify_verdicts":"verify can also answer HTTP 200 {\"valid\":false,\"reason\":\"possession_failed\"|\"issuer_signature_invalid\"|\"status_{status}\"|\"settlement_{status}\"} — a negative verdict, not a transport error."}}