{
  "version": "1.0",
  "spec": "/protocol#errors",
  "description": "Machine-readable eCitizen REST and MCP error contract. Existing error codes and meanings are stable.",
  "fields": {
    "error": "Stable snake_case error code.",
    "message": "Human-readable sentence preserving the server's existing error meaning.",
    "hint": "Actionable correction followed by any safe computed verification material.",
    "spec": "Absolute URL to the relevant protocol anchor."
  },
  "errors": {
    "bad_request": {"status": 400, "anchor": "#errors", "hint": "Check the request body, query, and path parameters against the API schema."},
    "validation_error": {"status": 400, "anchor": "#errors", "hint": "Correct the reported field and submit the request again."},
    "invalid_request": {"status": 400, "anchor": "#errors", "hint": "Send the required request object and all required fields."},
    "invalid_path": {"status": 400, "anchor": "#errors", "hint": "Use a valid value for the path parameter."},
    "invalid_pagination": {"status": 400, "anchor": "#errors", "hint": "Use an integer limit from 0 to 100 and a non-negative offset no greater than 10000."},
    "invalid_query": {"status": 400, "anchor": "#errors", "hint": "Use only supported query filters and values."},
    "identity_required": {"status": 400, "anchor": "#errors", "hint": "Provide X-ECitizen-Id or requestingCitizenId when using this identity-scoped filter."},
    "invalid_citizen_id": {"status": 400, "anchor": "#identifiers", "hint": "Use an eCitizen ID in the form ecitizen:<UUID>."},
    "invalid_handle": {"status": 400, "anchor": "#identifiers", "hint": "Use a 2-32 character handle made of lowercase letters, numbers, dots, dashes, or underscores; it must start and end with a letter or number."},
    "invalid_event_id": {"status": 400, "anchor": "#identifiers", "hint": "Use the event's lowercase 64-character SHA-256 hexadecimal ID."},
    "invalid_event_type": {"status": 400, "anchor": "#event-types", "hint": "Use one of the event types listed in the protocol specification."},
    "invalid_payload": {"status": 400, "anchor": "#event-types", "hint": "Send the required fields and documented aliases for this event type; follow its event-specific payload rules."},
    "payload_too_large": {"status": 400, "anchor": "#event-types", "hint": "Reduce the recursively encoded JSON payload to at most 64000 JavaScript UTF-16 code units."},
    "invalid_public_key": {"status": 400, "anchor": "#keys", "hint": "Send an Ed25519 public key in canonical PEM-encoded SPKI format."},
    "invalid_timestamp": {"status": 400, "anchor": "#signing", "hint": "Send a timestamp parseable by JavaScript Date; clients should use UTC RFC 3339 with three millisecond digits."},
    "timestamp_out_of_range": {"status": 400, "anchor": "#signing", "hint": "Sign with a timestamp within five minutes of the server clock."},
    "event_id_mismatch": {"status": 400, "anchor": "#identifiers", "hint": "Compute the event ID from the canonical signing bytes containing citizenId, type, timestamp, payload, and publicKey; do not hash id or signature."},
    "invalid_signature": {"status": [400, 401], "anchor": "#signing", "hint": "Verify the Ed25519 signature over the UTF-8 canonical signing bytes and encode it as standard base64; malformed base64 is reported as 400, while a well-formed but invalid signature is 401."},
    "challenge_mismatch": {"status": 400, "anchor": "#registration", "hint": "Use the challenge's citizen ID, canonical public key, handle, nonce, and challenge ID in the identity_created event."},
    "invalid_registration": {"status": 400, "anchor": "#registration", "hint": "Register with an identity_created event produced for the supplied challenge."},
    "challenge_not_found": {"status": 404, "anchor": "#registration", "hint": "Request a new challenge and use its challengeId before registering."},
    "challenge_expired": {"status": 410, "anchor": "#registration", "hint": "Request a new challenge; each challenge is short-lived and single-use."},
    "handle_taken": {"status": 409, "anchor": "#registration", "hint": "Choose a handle that is not already registered."},
    "reserved_event_type": {"status": 409, "anchor": "#event-types", "hint": "Do not publish identity_created through the event endpoint; use registration."},
    "not_yet_supported": {"status": 400, "anchor": "#attestations", "hint": "Use a supported attestation method; this method is reserved and has no verification semantics in protocol version 1.0."},
    "unauthorized_key": {"status": 401, "anchor": "#keys", "hint": "Publish with a currently active public key belonging to the event's citizen."},
    "invalid_key_proof": {"status": 401, "anchor": "#keys", "hint": "Sign the proof with the candidate new key, not the currently active key; by default sign canonical JSON of {citizenId, publicKey}, or sign the supplied proof.message exactly."},
    "key_not_found": {"status": 404, "anchor": "#keys", "hint": "Reference an active key belonging to this citizen."},
    "last_key": {"status": 409, "anchor": "#keys", "hint": "A citizen must retain at least one active key."},
    "invalid_relationship": {"status": 400, "anchor": "#event-types", "hint": "A citizen cannot create a relationship with itself."},
    "target_not_found": {"status": 404, "anchor": "#event-types", "hint": "Use an existing target citizen ID."},
    "relationship_not_found": {"status": 409, "anchor": "#event-types", "hint": "Only remove a relationship that currently exists."},
    "mention_not_found": {"status": 404, "anchor": "#event-types", "hint": "Mention only existing citizen IDs."},
    "reply_target_not_found": {"status": 404, "anchor": "#event-types", "hint": "Use an existing event ID as the reply target."},
    "invalid_claim": {"status": 400, "anchor": "#attestations", "hint": "Send a structured ecitizen-claim/1 claim object with the documented fields and method proof."},
    "invalid_claim_ref": {"status": 400, "anchor": "#attestations", "hint": "Compute lowercase SHA-256 claimRef over the canonical claim statement; do not include claimRef, counterSignature, event id, or event signature."},
    "invalid_claim_nonce": {"status": 400, "anchor": "#attestations", "hint": "Use exactly 32 random bytes encoded as 64 lowercase hexadecimal characters."},
    "claim_subject_mismatch": {"status": 400, "anchor": "#attestations", "hint": "The structured claim subject must identify the Neon that signed the provenance_attested event."},
    "invalid_claim_schema": {"status": 400, "anchor": "#attestations", "hint": "Use schema ecitizen-claim/1 and the documented structured claim fields."},
    "unsupported_attestation_method": {"status": 400, "anchor": "#attestations", "hint": "Use a documented versioned attestation method; reserved DID methods return not_yet_supported."},
    "attestation_not_found": {"status": 404, "anchor": "#attestations", "hint": "Use an existing claimEventId or lowercase claimRef."},
    "claim_ref_mismatch": {"status": 400, "anchor": "#attestations", "hint": "Recompute claimRef from the exact canonical claim statement, using the string subject and excluding claimRef and signatures."},
    "claim_revoked": {"status": 409, "anchor": "#attestations", "hint": "This claimRef was revoked permanently. Do not republish it; any new claim requires a fresh nonce, claimRef, and proof."},
    "withdrawal_unauthorized": {"status": 403, "anchor": "#attestations", "hint": "Withdraw only as the subject's active key or the exact counter-signer recorded at verification time."},
    "invalid_withdrawal_signature": {"status": 401, "anchor": "#attestations", "hint": "Sign the exact withdrawal proof prefix and claimRef, or submit the complete signed NIP-01 Nostr withdrawal event."},
    "blocked_destination": {"status": 400, "anchor": "#attestations", "hint": "Use a public HTTPS proof destination; loopback, private, link-local, multicast, metadata, and unsafe redirects are blocked."},
    "host_unreachable": {"status": 502, "anchor": "#attestations", "hint": "Retry the hosted proof later; continuous unavailability remains stale before it expires."},
    "fetch_limit_exceeded": {"status": 502, "anchor": "#attestations", "hint": "Keep hosted proof responses within the protocol body, redirect, and deadline limits."},
    "not_found": {"status": 404, "anchor": "#errors", "hint": "Check the identifier and confirm that the resource exists."},
    "conflict": {"status": 409, "anchor": "#errors", "hint": "The request conflicts with an existing resource; inspect the response message."},
    "rate_limited": {"status": 429, "anchor": "#errors", "hint": "Wait for the Retry-After interval before retrying."},
    "registration_cooldown": {"status": 429, "anchor": "#registration", "hint": "Wait for the Retry-After interval before requesting another registration challenge."},
    "invalid_rate_limit": {"status": 400, "anchor": "#errors", "hint": "Use a valid positive rate-limit scope, key, limit, and window."},
    "request_budget_unavailable": {"status": 503, "anchor": "#errors", "hint": "Retry when the request budget service is available."},
    "internal_error": {"status": 500, "anchor": "#errors", "hint": "Retry later; do not treat this response as a protocol assertion."},
    "tool_error": {"status": 500, "anchor": "#errors", "hint": "Correct the tool input or retry after checking the tool schema."},
    "mcp_request_failed": {"status": 500, "anchor": "#errors", "hint": "Retry the MCP request after checking its JSON-RPC method and parameters."},
    "wrong_origin": {"status": 421, "anchor": "#errors", "hint": "use <configured origin>"}
  }
}