Rate card schema
A rate card is a publisher's price list: per-retrieval prices for each usage tier, the default licence terms, and optional price rules. It is a standalone, versioned JSON document. Validation lives in src/ratecard/schema.ts; pricing in src/ratecard/pricing.ts (a pure function).
The rate card is deliberately separate from the entitlement (which carries the resolved terms that were sold and references the card only by rate_card_ref) and from telemetry (which references it as terms_ref).
Example
data/rate-cards/harbour-ledger.json:
{
"schema": "clear-exchange.rate-card",
"schema_version": "1.0",
"rate_card_id": "rc_harbour_ledger_std",
"version": 3,
"publisher_id": "pub_harbour_ledger",
"currency": "USD",
"effective_from": "2026-01-01T00:00:00Z",
"effective_until": null,
"default_terms": {
"citation_required": true,
"attribution": "The Harbour Ledger",
"max_snippet_chars": 320,
"retention_seconds": 86400,
"ai_training": "prohibited"
},
"tiers": [
{ "usage": "grounding_only", "price_micros": 4000 },
{ "usage": "display_snippet", "price_micros": 8000 },
{ "usage": "full_text", "price_micros": 20000, "terms": { "retention_seconds": 3600 } }
],
"rules": [
{ "id": "markets-premium", "match": { "sections": ["markets"] }, "multiplier_bps": 15000 },
{ "id": "archive-discount", "match": { "min_age_days": 365 }, "multiplier_bps": 5000 }
]
}
Fields
| Field | Type | Required | Meaning |
|---|---|---|---|
schema | string | yes | Always "clear-exchange.rate-card". |
schema_version | string | yes | "MAJOR.MINOR". This exchange reads any 1.x. |
rate_card_id | string | yes | Stable id of the card. |
version | integer ≥ 1 | yes | Monotonic revision. Offers and entitlements pin to rate_card_id@vN (rate_card_ref). |
publisher_id | string | yes | Must match the publisher of the source that uses the card. |
currency | string | yes | ISO 4217 code. All prices on the card are in micros of it. |
effective_from | date-time | yes | The card prices nothing before this. |
effective_until | date-time or null | no | The card prices nothing from this moment. |
default_terms | object | yes | Licence terms for every tier unless a tier overrides them. See Terms. |
tiers | array | yes | At least one; at most one per usage tier. Each is { usage, price_micros, terms?, extensions? }. price_micros is a non-negative integer. terms overrides fields of default_terms for that tier. |
rules | array | no | Ordered price rules. See Rules. |
extensions | object | no | Namespaced extra data, for example "com.clear-exchange" or "com.example". Allowed at the top level, per tier and per terms object. |
Usage tiers are grounding_only, display_snippet and full_text, narrowest first; each includes the rights of the narrower ones (see API conventions).
Terms
| Field | Type | Required | Meaning |
|---|---|---|---|
citation_required | boolean | yes | The agent must cite the source when it uses the content in an answer. |
retention_seconds | integer ≥ 0 | yes | How long the agent may retain or cache the content. Retrieval returns licence.retain_until. |
ai_training | string | yes | "prohibited", or "per_content_licence" for openly licensed content, where the open licence governs. |
attribution | string | no | Attribution text to show with citations. |
max_snippet_chars | integer | no | Most characters that may be displayed under display_snippet. |
content_licence | object | since 1.1; required when ai_training is "per_content_licence" | { id, name, url, share_alike, attribution_to? }. id uses SPDX where one exists, for example CC-BY-SA-4.0. |
Rules
Rules adjust a tier's price by content attributes. For each tier, every matching rule is applied in order:
fixed_price_micros(a map of tier to price) replaces the current price for the listed tiers;- then
multiplier_bpsmultiplies the current price (10000 = 1×, 15000 = 1.5×), rounding half up in integer micros.
match fields, all of which must hold (an empty match matches everything):
| Field | Matches when |
|---|---|
sections | The item's section is in the list. |
tags | The item has at least one of the tags. |
content_ids | The item's content_id is in the list. |
max_age_days | The item was published at most this many days ago. |
min_age_days | The item was published at least this many days ago. |
Age rules never match an item without published_at. The ids of the rules applied appear in the offer's licence.applied_rules.
Worked example with the card above: a markets article published two years ago, display_snippet: 8000 × 1.5 = 12000, then × 0.5 = 6000 micros (USD 0.006) publisher price, and applied_rules: ["markets-premium", "archive-discount"].
How an offer is priced
For a requested tier, the exchange:
- ignores the card if it is not in effect now;
- considers every tier at least as broad as the one requested, prices each with the rules, and quotes the cheapest (on a tie, the broader tier). So an agent asking for
display_snippetcan be soldfull_textif that is cheaper; the offer'slicence.usagesays what is sold; - treats the card as a ceiling: a source may offer an item below the card price for a tier (
price_breakdown.discounted: true), never above it. An offer above the card is priced at the card and counted instats.offers_above_card; - adds the clearing fee on top: fee =
floor(publisher price × CLEARING_FEE_BPS / 10000)(default 1000 bps, 10%, a placeholder), so rounding never favours the exchange. The agent'smax_priceapplies to the all-in price.
Offers whose card currency differs from the query's are excluded (stats.currency_mismatch); there is no conversion.
Compatibility rules
- Readers accept any
1.xdocument and ignore unknown fields. A new major version is a breaking change and is rejected at load. - Put publisher- or marketplace-specific data in
extensionsunder your own namespace, not in new top-level fields. - Version 1.1 added
content_licenceandai_training: "per_content_licence". The Wikimedia demo cards (data/rate-cards/wikinews-en.json,wikipedia-en.json) use 1.1; the others are 1.0.
Where rate cards come from
- Seeded catalogue publishers:
data/rate-cards/*.json, referenced fromdata/catalogue/publishers.json. http-offerpublishers: therate_cardfield of the source entry, a path or an inline object (see Adding a source).- Other adapters supply a card on each candidate (
SupplyCandidate.rate_card).
Cards are validated at startup; an invalid card stops the server before it listens. There is no API to upload or change a rate card yet (see Roadmap). Operators can read the current cards at GET /v1/admin/rate-cards.
Demo cards
Cards whose extensions["com.clear-exchange"] carries demo_pricing: true (Wikimedia) or demo_publisher: true (the fictional mock publishers) are demo listings. The Wikimedia prices are for the exchange's retrieval, entitlement and telemetry service; the Wikimedia Foundation and contributors are not parties to them and receive nothing. See Live sources.