Status: NORMATIVE
Version: 0.1
Wire Format: peac-policy/0.1
This document defines the normative specification for PEAC Policy Documents, served at /.well-known/peac.txt. Policy documents declare machine-readable terms for automated interactions: allowed purposes, receipt requirements, rate limits, and payment terms.
Key words: The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174 (when, and only when, they appear in all capitals).
Scope: This specification covers policy documents only. peac.txt is NOT a key discovery surface. Cryptographic key resolution MUST use the normative discovery chain: iss -> peac-issuer.json -> jwks_uri -> JWKS (see PEAC-ISSUER.md Section 3.4).
https://{domain}/.well-known/peac.txt
Implementations MUST check this location first.
https://{domain}/peac.txt
Implementations MAY check this location if the primary returns 404.
- Implementations MUST use HTTPS in production
- HTTP MAY be accepted only for localhost/127.0.0.1 in development environments
- Other schemes (file://, ftp://, etc.) MUST be rejected
A PEAC Policy Document is a structured object with the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
version |
string | Yes | Policy format version (e.g., peac-policy/0.1) |
usage |
enum | Yes | Access model: open or conditional |
purposes |
string[] | No | Allowed purposes (e.g., crawl, index, train) |
receipts |
enum | No | Receipt requirement: required, optional, or omit |
attribution |
enum | No | Attribution requirement: required, optional, or none |
rate_limit |
string | No | Rate limit (e.g., 100/hour, unlimited) |
daily_limit |
number | No | Daily request limit |
negotiate |
string | No | Negotiation endpoint URL |
contact |
string | No | Contact email or URL |
license |
string | No | License identifier (e.g., Apache-2.0) |
price |
number | No | Price per request (minor units) |
currency |
string | No | Currency code (ISO 4217) |
payment_methods |
string[] | No | Supported payment rails |
payment_endpoint |
string | No | Payment endpoint URL |
The version field indicates the policy format version using the pattern peac-policy/<major>.<minor>:
peac-policy/0.1- Current stable format
Implementations MUST:
- Reject versions that do not start with
peac-policy/ - Reject unknown major versions
- MAY accept higher minor versions if all invariants hold
| Value | Description |
|---|---|
open |
Default allow; receipts optional |
conditional |
Default deny/review; receipts typically required |
Well-known purpose tokens:
| Token | Description |
|---|---|
crawl |
Web crawling/scraping |
index |
Search engine indexing |
train |
AI/ML model training |
inference |
AI/ML inference/generation |
ai_input |
RAG/grounding (content as AI input) |
ai_index |
AI-powered search/indexing |
search |
Traditional search |
user_action |
Direct user-initiated action |
Custom purpose tokens MUST match the grammar: /^[a-z][a-z0-9_]*(?::[a-z][a-z0-9_]*)?$/
Implementations MUST ignore unknown fields rather than rejecting them. This enables forward-compatible evolution of the policy format.
Policy documents MAY be serialized as YAML or JSON.
# Example: Open documentation policy
version: 'peac-policy/0.1'
usage: open
purposes: [crawl, index, search]
attribution: optional
receipts: optional
rate_limit: unlimited
license: Apache-2.0{
"version": "peac-policy/0.1",
"usage": "open",
"purposes": ["crawl", "index", "search"],
"attribution": "optional",
"receipts": "optional",
"rate_limit": "unlimited",
"license": "Apache-2.0"
}Servers SHOULD set appropriate Content-Type headers:
- YAML:
text/plain; charset=utf-8ortext/yaml; charset=utf-8 - JSON:
application/json; charset=utf-8
Input: response (HTTP response with body)
Output: parsed policy object or error
1. Check Content-Type header:
IF Content-Type contains "application/json":
PARSE as JSON
RETURN parsed object
2. Examine first non-whitespace character:
first_char = body.trimStart()[0]
IF first_char == '{':
PARSE as JSON
ELSE:
PARSE as YAML
3. RETURN parsed object
When parsing YAML, implementations MUST enforce these restrictions:
| Feature | Rule | Reason |
|---|---|---|
Anchors (&) |
MUST reject | Prevents exponential expansion attacks |
Aliases (*) |
MUST reject | Prevents exponential expansion attacks |
Merge keys (<<) |
MUST reject | Avoids ambiguous merge semantics |
Custom tags (!tag) |
MUST reject | Security and portability |
| Non-core schemas | MUST reject | Deterministic parsing |
Multi-document (---) |
MUST reject (after first) | Single document only |
| Encoding | MUST be UTF-8 | Interoperability |
| Mapping keys | MUST be strings | JSON compatibility |
When parsing JSON, implementations MUST:
- Use strict JSON parsing (no trailing commas, no comments)
- Reject duplicate keys at the same level
- Accept only UTF-8 encoding
| Limit | Value | Reason |
|---|---|---|
| Maximum bytes | 256 KiB | DoS protection |
| Maximum nesting depth | 8 levels | Stack safety |
| Maximum array length | 1000 elements | Memory bounds |
| Maximum string length | 64 KiB | Memory bounds |
| Limit | Value | Reason |
|---|---|---|
| Maximum line count | 100 lines | Readability |
| Maximum purposes | 50 tokens | Practical limit |
Implementations MUST reject documents that:
- Missing
versionfield - Missing
usagefield - Have
usagenot in[open, conditional] - Have
versionwith unrecognized major version
| Field | Type Constraint |
|---|---|
version |
Non-empty string |
usage |
String in [open, conditional] |
purposes |
Array of strings |
receipts |
String in [required, optional, omit] |
attribution |
String in [required, optional, none] |
rate_limit |
String matching rate limit grammar |
price |
Non-negative number |
currency |
3-letter ISO 4217 code |
rate_limit = "unlimited" | count "/" period
count = 1*DIGIT
period = "second" | "minute" | "hour" | "day"
Examples: 100/hour, 1000/day, unlimited
Servers SHOULD set cache headers:
Cache-Control: public, max-age=3600
ETag: "abc123"Clients SHOULD:
- Cache policy documents for at least 1 hour
- Honor
Cache-ControlandETagheaders - Implement conditional requests (
If-None-Match)
Policy documents are publisher assertions, not verified claims. Clients MUST NOT treat policy fields as authorization grants.
When fetching policy documents, implementations MUST:
- Block private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
- Block loopback (127.0.0.0/8, ::1)
- Block link-local (169.254.0.0/16, fe80::/10)
- Block metadata endpoints (169.254.169.254)
- Enforce HTTPS in production
- Set reasonable timeouts (5-10 seconds)
All parsed fields MUST be validated before use. Never trust user-controlled input from policy documents for:
- URL construction (SSRF)
- File path construction (path traversal)
- Command construction (injection)
| Code | HTTP | Description |
|---|---|---|
E_POLICY_FETCH_FAILED |
502 | Policy document not fetchable (network error, 5xx) |
E_POLICY_NOT_FOUND |
404 | Policy document not found at primary or fallback path |
E_POLICY_INVALID |
400 | Policy document not valid YAML/JSON or fails schema |
E_POLICY_TIMEOUT |
504 | Policy document fetch timed out |
E_POLICY_TOO_LARGE |
413 | Policy document exceeds size limit |
E_POLICY_SSRF_BLOCKED |
403 | SSRF protection blocked the fetch |
E_POLICY_YAML_INJECTION |
400 | Rejected YAML anchors, aliases, merge keys, or tags |
E_POLICY_MULTI_DOCUMENT |
400 | Multi-document YAML not allowed |
E_POLICY_INSECURE_SCHEME |
403 | Non-HTTPS URL in production |
version: 'peac-policy/0.1'
usage: open
purposes: [crawl, index, search, ai_index]
attribution: optional
receipts: optional
rate_limit: unlimited
license: Apache-2.0
contact: docs@example.comversion: 'peac-policy/0.1'
usage: conditional
purposes: [inference, ai_input]
receipts: required
rate_limit: 100/hour
daily_limit: 1000
price: 10
currency: USD
payment_methods: [x402, stripe]
payment_endpoint: https://api.example.com/pay
negotiate: https://api.example.com/negotiate
contact: api-support@example.comversion: 'peac-policy/0.1'
usage: conditional
purposes: [crawl, index, search]
receipts: required
attribution: required
rate_limit: 60/minute
contact: licensing@news.example.com| Specification | Relationship |
|---|---|
| PEAC-ISSUER.md | Issuer configuration (JWKS, verification) |
| PROTOCOL-BEHAVIOR.md | Receipt issuance and verification |
| robots.txt | Complementary crawl directives |
| ai.txt / llm.txt | Complementary AI usage signals |
An L0-conformant implementation MUST:
- Fetch policy from well-known locations
- Detect YAML vs JSON format
- Parse with subset restrictions
- Enforce size limits
- Validate required fields
- Ignore unknown fields
An L1-conformant implementation MUST also:
- Evaluate purpose matching
- Apply rate limit parsing
- Resolve receipt requirements
- Handle caching correctly
| Version | Date | Changes |
|---|---|---|
| 0.1 | 2026-01-14 | Initial specification |
- RFC 2119 - Key words for use in RFCs
- RFC 8174 - Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words
- RFC 8259 - The JavaScript Object Notation (JSON) Data Interchange Format
- YAML 1.2 - YAML Ain't Markup Language
- RFC 8615 - Well-Known Uniform Resource Identifiers (URIs)
- PEAC-ISSUER.md - Issuer Configuration Specification