The Club Good Protocol
Price Discovery and Enforcement over HTTP
The economics of the open web have been distorted by the rise of AI agents and crawlers. Content, which was once monetized by turning limited space and reader attention into advertising value, is now treated as if it were infinite and free.
To rebalance the system, publishers need mechanisms to define and sell inventory that agents recognize as scarce and valuable.
To restore a fair marketplace to the open Web we can apply fundamental principles of economics to select a model that works for these new realities. The model that works best is to treat Web resources as a Club Good.
In order for this Club Good approach to be as open and scalable as the Web itself, we propose a small extension to the HTTP that we call The Club Good Protocol. This protocol extension is the minimum work necessary to establish the Web as a Club Good and is independent of business model, pricing model or payment method.
Technical Specification
Overview
We use HTTP itself to expose live prices, capture consent, and meter usage. Publishers (or paywalls.net at the edge) advertise current floors and upcoming changes in response headers. When an AI agent proceeds and the server returns content, that act is the binding acceptance for that unit price. Every served response is logged (price, actor, resource, timestamp, receipt id) and later rolled up for billing. No inline payment or payment tokens are required.
Design Principles
- Low friction at scale. No per-request payment handshakes; agents can make millions of requests/day.
- Visible terms, auditable records. Each served response carries the price charged plus a receipt id; the server persists immutable logs.
- Deterministic enforcement. If a request doesn’t meet the current floor or violates a window/slot, the origin replies with a hard signal (e.g., 402/403) and machine-readable guidance.
- Interoperable by default. Use ordinary HTTP verbs, headers, HTTP Structured Fields, RFC 7807 Problem Details JSON where helpful, and conservative cache/
Varyrules.
Open issues
Caching
This working draft does not currently consider the case where resources are cached and used without additional request to the content server. In this case “usage reporting” may need to be specified by the content server via hypermedia of some kind (eg. a Link response header), analogous to “tracking pixels” in HTML pages.
Authentication
This working draft assumes existing means of HTTP based authentication are employed. For example using Bearer tokens in an Authentication request header as well as WWW-Authenticate response header with OAuth2 fields like resource_metadata="https://paywalls.net/.well-known/oauth-protected-resource"
Actors & Identity
- Publisher Origin (or paywalls.net enforcement layer at CDN/WAF/edge).
- Agent Operator (requests content, accepts terms on behalf of their customers).
- Agent Identity: conveyed via standard auth (e.g.,
Authorization: Bearer <token>or mTLS). Identity is required for account mapping and billing.
Core Protocol Headers
Price signals
Pricing
Conveys the current price context for the response (or quote on 402): the applied price (when served), the current floor, and any scheduled change. Acts as the sole source of truth for pricing metadata in this response.
Structured field (sf-dictionary) with price applied, unit, currency, floor, etc. See Worked Examples below.
Pricing : applied=<sf-decimal>, unit=request|cpm, currency="<ISO-4217>", floor=<sf-decimal>, next_floor=<sf-decimal>, effective=<sf-date>, valid_until=<sf-date>, window_start=<sf-date>, window_end=<sf-date>, version=1
Keys
version:sf-integer— pricing schema version (e.g.,1)applied:sf-decimal— price actually applied for this response (present on 200/206;0during prepaid first-look windows) Note : sf-decimal supports up to 3 digit precisioncurrency: ISO-4217 code, e.g."USD"unit:"request"or"cpm"floor:sf-decimal— current minimum acceptable price (perunit)valid_until:sf-date— commitment thatfloorwon’t change before this time (short stickiness window to reduce probing)next_floor:sf-decimal— the next floor value that will take effecteffective:sf-date— UNIX epoch seconds whennext_floorbecomes activerank:sf-integer— exclusivity rank (first-look slot), if applicablewindow_start/window_end:sf-date— exclusivity window bounds, if applicableReceipt-Id : <opaque-id>: Durable transaction id for logging, audit, and dispute resolution.Vary : Authorization, If-Price-LTE: Ensures intermediaries don’t leak paid responses across identities or price constraints.RateLimit-*: (optional; standard family) Exposure control without changing price.
Potential future keys
class: opaque price class key (e.g."weather.ski:*", "news.elections:*")
Client Guardrail Header (optional)
If-Price-LTE
Structured field (Item, with parameters). The agent’s cap for this call. If the live floor exceeds this, the origin returns 402 with terms instead of serving and charging.
If-Price-LTE : <amount>; unit=request|cpm; currency="<ISO-4217>"
Usage Based Pricing
To support differential pricing based on “crawling” vs. “agentic” use cases, some server implementations may choose to vary Pricing values not only by the requested resource (URL) but also by the declared or inferred usage intent of the requesting agent. For example, a publisher might classify user-agents by purposes such as “Learn” (insights/analytics, indexing, AI model training) or “Apply” (interactive agents, end-user applications), and apply different floors accordingly (or none at all).
This is outside the scope of the core protocol, but compatible with it: the Pricing header is the single source of truth at response time, regardless of the policy inputs used to calculate it.
Request/Response Flows
A) Price Discovery & Base Access (Floor Pricing)
Price-capped request (safe-serve if price ≤ cap)
GET /snow/alta/2025-01-10 HTTP/1.1Host: example.comAccept: application/jsonIf-Price-LTE: 0.003; unit=request; currency=USDAuthorization: Bearer agt_XYZIdempotency-Key: 1f7c1e24-1d1d-4a6b-9a4b-7b2b4f5c9e2a
Response (served; consent captured; logged for invoice)
HTTP/1.1 200 OKContent-Type: application/jsonPricing: applied=0.003, unit=request, currency=USD, next_floor=0.005, effective=@1743552000Receipt-Id: rcpt_7Y2fVary: Authorization, If-Price-LTE
{"base_inches": 40}
If the server’s live floor is above the client’s cap, it does not serve content and returns a quote:
Response (terms only; no content)
HTTP/1.1 402 Payment RequiredContent-Type: application/problem+jsonPricing: floor=0.005, unit=request, currency=USD, next_floor=0.008, effective=@1743552000Retry-After: 60
{"type": "about:blank","title": "Price Floor Not Met","detail": "Live floor exceeds your If-Price-LTE cap.","resource": "/snow/alta/2025-01-10","current_floor": {"amount":"0.005","unit":"request","currency":"USD"}}
B) Quality: Sampling + Dynamic Floors (Dynamic Reserve Pricing)
Early sampling (low entry to allow utility discovery)
HTTP/1.1 200 OKPricing: applied=0.010, unit=request, next_floor=0.050, effective=@1743552000Receipt-Id: rcpt_quality_001
Later (demand continues at a meaningful pace; floor ratchets)
HTTP/1.1 200 OKPricing: applied=0.050, unit=request, next_floor=0.100, effective=@1743552000Receipt-Id: rcpt_quality_099
Agents that can justify the higher price stay; others drop—revealing the demand curve.
C) First Look: Daily Options Auction → Ranked Slots
How it works: Separate from the content access requests, agents bid in a daily auction for ranked “first look” slots on premium subjects (eg. politics, real-estate).
- Each day per subject (e.g., politics, real-estate, sports), the publisher runs an options auction that assigns ranked exclusivity windows for tomorrow’s content.
- Winners are registered in the publisher’s account ledger; no per-request payment is required during their slot.
- Non-winners can still access outside windows at the live floor (base or quality pricing).
Within a winner’s slot (served at $0; option premium covers it)
GET /elections/iowa/results/live HTTP/1.1Authorization: Bearer agt_POLITICOAccept: application/json
HTTP/1.1 200 OKContent-Type: application/jsonPricing: applied=0.000, note="consumed option premium", rank=1, window_start=@1743552000, window_end=@1743595200Receipt-Id: rcpt_caucus_001
Outside the slot (floor applies; visible pricing; consent by access)
HTTP/1.1 200 OKContent-Type: application/jsonPricing: applied=0.020, unit=request, currency=USD, next_floor=0.030, effective=@1743552000Receipt-Id: rcpt_caucus_119
If a client attempts early access without the winning entitlement, the origin responds with a hard block (no ambiguity):
HTTP/1.1 403 ForbiddenPricing: window_start=@1743552000, rank=2
Client Controls
To safely automate interactions with priced resources and avoid unintended costs, clients should:
- Use If-Price-LTE to cap per-call willingness-to-pay; if per-resource floors are higher then the client's willingness to pay, the server returns 402 with terms instead of charging.
- Use Idempotency-Key so retried requests don’t create duplicate billing line items.
Caching & Intermediaries
- No shared cache for priced content across identities. Use
Vary: Authorization, If-Price-LTE. - Short-lived client caches may store Price-Floor and Price-Next-Min for planning, but serving decisions must be made per request.
- The
Pricingheader is response-specific; do not cache and replay it across identities.
Note: Cached content that is used by clients should report that usage via a hypermedia mechanism specified by the origin (eg. a Link header), analogous to “tracking pixels” in HTML pages. This will be specified in a future version of this document.
Versioning & Backward Compatibility
- Supports a
Pricing: version=1response header field. - New headers added conservatively; unknown headers must be ignored by clients.
Security & Integrity
- Auth: standard bearer tokens or mTLS
- Signed Receipts (optional):
Receipt-Signatureheader using HTTP Message Signatures or JWS over a canonical receipt string. - Optionally sign a canonical string of
Pricingwith HTTP Message Signatures or JWS to make disputes easy. - Replay safety:
Idempotency-Key+ server dedupe.
Error Semantics (Summary)
- 200 OK → content served;
Pricing: applied=nnnpresent; consent captured; logged. - 402 Payment Required → not served; price terms disclosed (e.g., cap not met, floor quoted).
- 403 Forbidden → disallowed by policy (e.g., outside first-look slot, blocked actor).
- 429 Too Many Requests → throttling without price changes.
- 5xx → server errors; clients may retry with the same
Idempotency-Key.
Worked Examples
Example 1 — Base Access with CPM
Request hits a resource server endpoint.
GET /snow/alta/2025-01-10 HTTP/1.1Host: example.comAccept: application/jsonIf-Price-LTE: 8.0; unit=cpm; currency=USDAuthorization: Bearer agt_XYZIdempotency-Key: 1f7c1e24-1d1d-4a6b-9a4b-7b2b4f5c9e2a
HTTP/1.1 200 OKPricing: applied=4.0, unit=cpm, currency=USD, next_floor=4.2, effective=@1743552000Receipt-Id: rcpt_base_445
Example 2 — Quality Ratchet
Sustained demand moves the floor; each served response discloses current and next steps.
HTTP/1.1 200 OKPricing: applied=0.020, unit=request, next_floor=0.050, effective=@1743552000Receipt-Id: rcpt_q_077
Later:
HTTP/1.1 200 OKPricing: applied=0.050, unit=request, next_floor=0.100, effective=@1743552000Receipt-Id: rcpt_q_118
Example 2 — First Look (Daily Subject Auction)
Agent won rank-2 for “tomorrow: real-estate/new-listings” (10-minute slot after rank-1). During its window:
HTTP/1.1 200 OKPricing: applied=0.000, note="consumed option premium", rank=2, window_start=@1743552000, window_end=@1743595200Receipt-Id: rcpt_re_112
Outside window, normal floors apply:
HTTP/1.1 200 OKPricing: applied=0.025, unit=requestReceipt-Id: rcpt_re_143
Appendix
Example responses
200 OK — Base access (served at floor)
Pricing: applied=0.005, currency="USD", unit="request", floor=0.005, next_floor=0.008, effective=@1743552000, valid_until=@1743595200, version=1Vary: Authorization
402 Payment Required — Quote only (client cap too low)
Pricing: floor=0.020, currency="USD", unit="request", next_floor=0.030, effective=@1743552000, valid_until=@1743595200, version=1Content-Type: application/problem+json
(Body can use RFC 7807 to restate the quote; no duplication necessary.)
200 OK — Quality ratchet (applied above floor; future step signaled)
Pricing: applied=0.050, unit="request", currency="USD", floor=0.020, next_floor=0.100, effective=@1743552000, valid_until=@1743595200, version=1
200 OK — First look (prepaid option window; applied is zero)
Pricing: applied=0.000, unit="request", currency="USD", floor=0.000, rank=1, window_start=@1743552000, window_end=@1743595200, version=1
200 OK — CPM expression
Pricing: applied=5.00, unit="cpm", currency="USD", floor=5.00, next_floor=8.00, effective=@1743552000, valid_until=@1743595200, version=1