Normative error codes for PEAC Protocol v0.11.3+.
| Code | Category | Severity | Retryable | Next Action | HTTP | Description | Remediation |
|---|
| Code | Category | Severity | Retryable | Next Action | HTTP | Description | Remediation |
|---|---|---|---|---|---|---|---|
E_CONTROL_REQUIRED |
validation | error | false | retry_with_different_input |
400 | Control block required when payment present or enforcement.method==http-402 | Add control{} block to auth context |
E_INVALID_ENVELOPE |
validation | error | false | retry_with_different_input |
400 | Receipt envelope structure is invalid | Ensure envelope has auth, evidence, and meta blocks |
E_INVALID_CONTROL_CHAIN |
validation | error | false | retry_with_different_input |
400 | Control chain is invalid or inconsistent | Ensure chain is non-empty and decision matches chain results |
E_INVALID_PAYMENT |
validation | error | false | retry_with_different_input |
400 | Payment evidence is malformed or incomplete | Verify payment has required fields (scheme, reference, amount, currency, asset, env) |
E_INVALID_POLICY_HASH |
validation | error | false | retry_with_different_input |
400 | Policy hash does not match policy content | Recompute policy_hash as base64url(sha256(JCS(policy))) |
E_EXPIRED_RECEIPT |
validation | error | false | retry_with_different_input |
401 | Receipt exp claim is in the past | Use a current receipt |
| Code | Category | Severity | Retryable | Next Action | HTTP | Description | Remediation |
|---|---|---|---|---|---|---|---|
E_INVALID_SIGNATURE |
verification | error | false | abort |
401 | JWS signature verification failed | Ensure receipt is signed with correct private key and alg=EdDSA |
E_SSRF_BLOCKED |
verification | error | false | abort |
403 | SSRF protection blocked request to private/metadata IP | Use only public HTTPS URLs for JWKS/policy endpoints |
E_DPOP_REPLAY |
verification | error | false | retry_with_different_input |
403 | DPoP nonce has already been used | Generate a fresh DPoP proof with new nonce |
E_DPOP_INVALID |
verification | error | false | retry_with_different_input |
403 | DPoP proof is invalid or malformed | Ensure DPoP JWT has correct claims (jkt, iat, htm, htu) |
| Code | Category | Severity | Retryable | Next Action | HTTP | Description | Remediation |
|---|---|---|---|---|---|---|---|
E_CONTROL_DENIED |
control | error | false | contact_issuer |
403 | Control decision denied | Check control chain for reason |
| Code | Category | Severity | Retryable | Next Action | HTTP | Description | Remediation |
|---|---|---|---|---|---|---|---|
E_JWKS_FETCH_FAILED |
infrastructure | error | true | retry_after_delay |
502 | Failed to fetch JWKS from issuer | Retry after delay; check JWKS URL is accessible |
E_POLICY_FETCH_FAILED |
infrastructure | error | true | retry_after_delay |
502 | Failed to fetch policy from policy_uri | Retry after delay; check policy URI is accessible |
E_NETWORK_ERROR |
infrastructure | error | true | retry_after_delay |
502 | Generic network/transport failure | Retry after delay |
E_RATE_LIMITED |
infrastructure | error | true | retry_after_delay |
429 | Rate limit exceeded | Retry after Retry-After header value |
PEAC follows standard HTTP semantics for authentication and authorization errors:
| Status | Meaning | When to Use | Client Action |
|---|---|---|---|
| 401 | Unauthorized (missing/invalid creds) | Attestation missing, expired, not yet valid, signature invalid | Retry with valid attestation |
| 403 | Forbidden (denied by policy) | Authenticated but control decision was deny | Do not retry with same credentials |
Key distinction:
- 401: "I don't know who you are" or "your credentials are invalid" - client CAN retry with different/renewed credentials
- 403: "I know who you are, but you're not allowed" - retrying with same identity won't help
Attestations (agent identity, attribution, dispute) use 401 for temporal validity errors, while receipts use 400. This distinction reflects their different roles:
| Credential Type | Temporal Error | HTTP Status | Rationale |
|---|---|---|---|
| Receipt | E_EXPIRED, E_NOT_YET_VALID | 400 | Receipts are data objects - temporal issues are validation failures |
| Attestation | E_{TYPE}_EXPIRED, E_{TYPE}_NOT_YET_VALID |
401 | Attestations are credentials - temporal issues are authentication failures |
Affected error codes:
E_IDENTITY_NOT_YET_VALID,E_IDENTITY_EXPIRED(401)E_ATTRIBUTION_NOT_YET_VALID,E_ATTRIBUTION_EXPIRED(401)E_DISPUTE_NOT_YET_VALID,E_DISPUTE_EXPIRED(401)
This convention treats attestations as token-like credentials that authenticate the presenter. An expired or not-yet-valid attestation is analogous to an expired OAuth token - an auth failure (401), not a format error (400).
Per RFC 9110 Section 15.5.2, 401 responses
MUST include a WWW-Authenticate header with at least one authentication challenge.
PEAC defines the PEAC-Attestation authentication scheme for attestation-based authentication:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: PEAC-Attestation realm="peac", attestation_type="identity"
Content-Type: application/problem+json
{
"type": "https://www.peacprotocol.org/errors#E_IDENTITY_EXPIRED",
"title": "Identity Attestation Expired",
"status": 401,
"detail": "The agent identity attestation has expired.",
"instance": "/api/resource/123"
}Parameters follow RFC 9110 auth-param syntax:
parameter = token "=" ( token / quoted-string ).
| Parameter | Required | Type | Values | Description |
|---|---|---|---|---|
realm |
Yes | quoted-string | "peac" |
Fixed realm identifier |
attestation_type |
Yes | token | identity, attribution, dispute |
Attestation type required/failed |
error |
No | token | Error code (e.g., expired) |
Machine-readable error category |
error_description |
No | quoted-string | Human-readable message | Explanation for debugging |
Example challenges:
# Missing agent identity attestation
WWW-Authenticate: PEAC-Attestation realm="peac", attestation_type=identity
# Expired identity attestation
WWW-Authenticate: PEAC-Attestation realm="peac", attestation_type=identity, error=expired, error_description="Attestation expired at 2026-01-06T12:00:00Z"
# Invalid signature on attribution attestation
WWW-Authenticate: PEAC-Attestation realm="peac", attestation_type=attribution, error=invalid_signature
# Dispute attestation not yet valid
WWW-Authenticate: PEAC-Attestation realm="peac", attestation_type=dispute, error=not_yet_validError tokens (for error parameter):
| Token | Meaning |
|---|---|
missing |
No attestation provided |
expired |
Attestation exp in the past |
not_yet_valid |
Attestation iat in the future |
invalid_signature |
Signature verification failed |
invalid_format |
Malformed attestation structure |
key_unknown |
Signing key not found |
key_expired |
Signing key expired |
key_revoked |
Signing key revoked |
Implementations MUST include the WWW-Authenticate header when returning 401 for attestation errors.
Failure to include this header violates HTTP semantics and may cause interoperability issues.
See also: Error Catalog for RFC 9457 response format.
All errors MUST be returned in this JSON structure:
{
"code": "E_CONTROL_REQUIRED",
"category": "validation",
"severity": "error",
"retryable": false,
"next_action": "retry_with_different_input",
"http_status": 400,
"pointer": "/auth/control",
"remediation": "Add control{} block when payment{} is present",
"details": {
"payment_present": true,
"control_present": false
}
}- code (string, required): Error code from registry
- category (string, required):
validation|verification|infrastructure|control|attribution|identity|dispute - severity (string, required):
error|warning - retryable (boolean, required): Whether client should retry
- next_action (string, required): Agent recovery hint (see Section 3)
- http_status (number, optional): Suggested HTTP status code
- pointer (string, optional): RFC 6901 JSON Pointer to problematic field
- remediation (string, optional): Human-readable fix guidance
- details (object, optional): Implementation-specific error context
To add a new error code:
- Choose a unique code following pattern
E_{CATEGORY}_{SPECIFIC} - Add to this registry table
- Add to
packages/schema/src/errors.tsERROR_CODES constant - Add test vectors demonstrating the error
- Document in relevant package README
The InteractionEvidenceV01 extension uses layered error codes with specific validation precedence.
These semantics are normative for conformant implementations.
| Prefix | Layer | Meaning |
|---|---|---|
E_INTERACTION_* |
Profile validation | Schema/format errors in the interaction extension |
W_INTERACTION_* |
Warnings | Non-fatal issues (valid but potentially problematic) |
Validators MUST check in this order, returning the first error encountered:
- Type check: Input must be a plain object (not null, not array)
- Required fields:
interaction_id,kind,executor.platform,started_at - Field format: Regex patterns, length limits, datetime parsing
- Reserved prefixes:
peac.*andorg.peacprotocol.*kinds not in registry - Target consistency:
tool.*requirestoolfield;http.*/fs.*requiresresource.uri - Timing invariants:
completed_at >= started_at - Error detail requirement:
result.status=errorrequireserror_codeor extensions
These validation behaviors are intentional and stable:
| Input | Error Code | Rationale |
|---|---|---|
Array.isArray(input) |
E_INTERACTION_INVALID_FORMAT |
Arrays are objects but not valid input |
kind: "" |
E_INTERACTION_MISSING_KIND |
Empty string is semantically missing |
started_at: "not-a-date" |
E_INTERACTION_MISSING_STARTED_AT |
Unparseable datetime is unusable |
completed_at < started_at |
E_INTERACTION_INVALID_TIMING |
Relational timing constraint violation |
resource: {} for http.*/fs.* |
E_INTERACTION_MISSING_TARGET |
Must have meaningful uri field |
Key distinction: E_INTERACTION_INVALID_TIMING is reserved exclusively for relational
constraints between fields (e.g., completed_at before started_at). Format/parsing
errors use E_INTERACTION_MISSING_* codes.
| Code | HTTP | Description |
|---|---|---|
E_INTERACTION_INVALID_FORMAT |
400 | Input is not a valid object |
E_INTERACTION_MISSING_ID |
400 | Missing or empty interaction_id |
E_INTERACTION_MISSING_KIND |
400 | Missing or empty kind |
E_INTERACTION_INVALID_KIND_FORMAT |
400 | Kind fails format validation |
E_INTERACTION_KIND_RESERVED |
400 | Kind uses reserved prefix not in registry |
E_INTERACTION_MISSING_EXECUTOR |
400 | Missing executor or executor.platform |
E_INTERACTION_MISSING_STARTED_AT |
400 | Missing or unparseable started_at |
E_INTERACTION_INVALID_TIMING |
400 | completed_at < started_at |
E_INTERACTION_MISSING_TARGET |
400 | Kind requires target field not present |
E_INTERACTION_INVALID_DIGEST |
400 | Digest value not 64 lowercase hex |
E_INTERACTION_INVALID_DIGEST_ALG |
400 | Unknown digest algorithm |
E_INTERACTION_MISSING_RESULT |
400 | Output present but no result.status |
E_INTERACTION_MISSING_ERROR_DETAIL |
400 | Error status without error_code or extensions |
E_INTERACTION_INVALID_EXTENSION_KEY |
400 | Extension key not properly namespaced |
| Code | Description |
|---|---|
W_INTERACTION_KIND_UNREGISTERED |
Kind not in well-known registry |
W_INTERACTION_MISSING_TARGET |
No tool or resource field (non-strict kind) |
Every error definition includes a next_action field providing best-effort
guidance for automated agents on how to recover from the error.
| Value | Semantics |
|---|---|
retry_after_delay |
Transient failure; retry the same request after a backoff period |
retry_with_different_key |
Signing key not found; try an alternative key |
retry_with_different_input |
Input validation failed; correct the request and retry |
refresh_attestation |
Attestation expired or revoked; obtain a fresh attestation |
contact_issuer |
Policy or control denial; contact the issuer for resolution |
abort |
Unrecoverable; do not retry (e.g., signature verification failed) |
none |
No recovery hint available |
These values are best-effort guidance hints, not normative protocol promises.
- Servers MAY change the
next_actionmapping for any error code between minor versions without breaking the protocol. - Agents SHOULD NOT build hard dependencies on specific
next_actionvalues for specific error codes. - The
retryablefield remains the authoritative signal for whether a retry is semantically valid.next_actionprovides additional context about how to retry, butretryable: falsetakes precedence. - The conformance fixture at
specs/conformance/fixtures/errors/next-action-hints.jsondocuments the current mapping but is explicitly labeled as a hint table, not a normative coupling.
In MCP tool responses, error structured content includes both fields:
{
"_meta": { "serverVersion": "0.11.2" },
"ok": false,
"code": "E_JWKS_FETCH_FAILED",
"message": "Failed to fetch JWKS",
"retryable": true,
"next_action": "retry_after_delay"
}- v0.11.2: Added
next_actionagent recovery hints (DD-132, DD-133); renamedretriabletoretryable(DD-134) - v0.10.7: Added Interaction Evidence error codes and validation semantics
- v0.9.15: Initial error registry with structured error model