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

  1. Low friction at scale. No per-request payment handshakes; agents can make millions of requests/day.
  2. Visible terms, auditable records. Each served response carries the price charged plus a receipt id; the server persists immutable logs.
  3. 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.
  4. Interoperable by default. Use ordinary HTTP verbs, headers, HTTP Structured Fields, RFC 7807 Problem Details JSON where helpful, and conservative cache/Vary rules.

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; 0 during prepaid first-look windows) Note : sf-decimal supports up to 3 digit precision
  • currency: ISO-4217 code, e.g. "USD"
  • unit: "request" or "cpm"
  • floor: sf-decimal — current minimum acceptable price (per unit)
  • valid_until: sf-date — commitment that floor won’t change before this time (short stickiness window to reduce probing)
  • next_floor: sf-decimal — the next floor value that will take effect
  • effective: sf-date — UNIX epoch seconds when next_floor becomes active
  • rank: sf-integer — exclusivity rank (first-look slot), if applicable
  • window_start / window_end: sf-date — exclusivity window bounds, if applicable
  • Receipt-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.1
Host: example.com
Accept: application/json
If-Price-LTE: 0.003; unit=request; currency=USD
Authorization: Bearer agt_XYZ
Idempotency-Key: 1f7c1e24-1d1d-4a6b-9a4b-7b2b4f5c9e2a

Response (served; consent captured; logged for invoice)

HTTP/1.1 200 OK
Content-Type: application/json
Pricing: applied=0.003, unit=request, currency=USD, next_floor=0.005, effective=@1743552000
Receipt-Id: rcpt_7Y2f
Vary: 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 Required
Content-Type: application/problem+json
Pricing: floor=0.005, unit=request, currency=USD, next_floor=0.008, effective=@1743552000
Retry-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 OK
Pricing: applied=0.010, unit=request, next_floor=0.050, effective=@1743552000
Receipt-Id: rcpt_quality_001

Later (demand continues at a meaningful pace; floor ratchets)

HTTP/1.1 200 OK
Pricing: applied=0.050, unit=request, next_floor=0.100, effective=@1743552000
Receipt-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.1
Authorization: Bearer agt_POLITICO
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json
Pricing: applied=0.000, note="consumed option premium", rank=1, window_start=@1743552000, window_end=@1743595200
Receipt-Id: rcpt_caucus_001

Outside the slot (floor applies; visible pricing; consent by access)

HTTP/1.1 200 OK
Content-Type: application/json
Pricing: applied=0.020, unit=request, currency=USD, next_floor=0.030, effective=@1743552000
Receipt-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 Forbidden
Pricing: 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 Pricing header 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=1 response 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-Signature header using HTTP Message Signatures or JWS over a canonical receipt string.
  • Optionally sign a canonical string of Pricing with HTTP Message Signatures or JWS to make disputes easy.
  • Replay safety: Idempotency-Key + server dedupe.

Error Semantics (Summary)

  • 200 OK → content served; Pricing: applied=nnn present; 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.1
Host: example.com
Accept: application/json
If-Price-LTE: 8.0; unit=cpm; currency=USD
Authorization: Bearer agt_XYZ
Idempotency-Key: 1f7c1e24-1d1d-4a6b-9a4b-7b2b4f5c9e2a

HTTP/1.1 200 OK
Pricing: applied=4.0, unit=cpm, currency=USD, next_floor=4.2, effective=@1743552000
Receipt-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 OK
Pricing: applied=0.020, unit=request, next_floor=0.050, effective=@1743552000
Receipt-Id: rcpt_q_077

Later:

HTTP/1.1 200 OK
Pricing: applied=0.050, unit=request, next_floor=0.100, effective=@1743552000
Receipt-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 OK
Pricing: applied=0.000, note="consumed option premium", rank=2, window_start=@1743552000, window_end=@1743595200
Receipt-Id: rcpt_re_112

Outside window, normal floors apply:

HTTP/1.1 200 OK
Pricing: applied=0.025, unit=request
Receipt-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=1
Vary: 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=1
Content-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