Clear ExchangeDocs Live demo

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

FieldTypeRequiredMeaning
schemastringyesAlways "clear-exchange.rate-card".
schema_versionstringyes"MAJOR.MINOR". This exchange reads any 1.x.
rate_card_idstringyesStable id of the card.
versioninteger ≥ 1yesMonotonic revision. Offers and entitlements pin to rate_card_id@vN (rate_card_ref).
publisher_idstringyesMust match the publisher of the source that uses the card.
currencystringyesISO 4217 code. All prices on the card are in micros of it.
effective_fromdate-timeyesThe card prices nothing before this.
effective_untildate-time or nullnoThe card prices nothing from this moment.
default_termsobjectyesLicence terms for every tier unless a tier overrides them. See Terms.
tiersarrayyesAt 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.
rulesarraynoOrdered price rules. See Rules.
extensionsobjectnoNamespaced 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

FieldTypeRequiredMeaning
citation_requiredbooleanyesThe agent must cite the source when it uses the content in an answer.
retention_secondsinteger ≥ 0yesHow long the agent may retain or cache the content. Retrieval returns licence.retain_until.
ai_trainingstringyes"prohibited", or "per_content_licence" for openly licensed content, where the open licence governs.
attributionstringnoAttribution text to show with citations.
max_snippet_charsintegernoMost characters that may be displayed under display_snippet.
content_licenceobjectsince 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:

  1. fixed_price_micros (a map of tier to price) replaces the current price for the listed tiers;
  2. then multiplier_bps multiplies 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):

FieldMatches when
sectionsThe item's section is in the list.
tagsThe item has at least one of the tags.
content_idsThe item's content_id is in the list.
max_age_daysThe item was published at most this many days ago.
min_age_daysThe 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:

  1. ignores the card if it is not in effect now;
  2. 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_snippet can be sold full_text if that is cheaper; the offer's licence.usage says what is sold;
  3. 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 in stats.offers_above_card;
  4. 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's max_price applies 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.x document and ignore unknown fields. A new major version is a breaking change and is rejected at load.
  • Put publisher- or marketplace-specific data in extensions under your own namespace, not in new top-level fields.
  • Version 1.1 added content_licence and ai_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 from data/catalogue/publishers.json.
  • http-offer publishers: the rate_card field 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.