Skip to content

JSONL Ingestion Feed

The discovery paths (RSL, sitemap, well-known, crawl, …) let an Exchange pull a publisher’s licensing signals. The JSONL feed is the push counterpart: a publisher hands the Exchange a JSON-Lines file — one resource per line — and each line maps 1:1 onto a ResourceEntry and is submitted via CatalogService.PushResources. The reference client is the ramp-ingest binary.

publisher feed (.jsonl, one resource/line)
└─► ramp-ingest parse (typed, strict) · canonicalize tokens · sign (RFC 9421)
└─► CatalogService.PushResources ── ALL-OR-NOTHING ──
│ trust: caller key fetched from /.well-known/ramp.json; tenant derived from the publisher domain
│ validate: protovalidate CEL owns term shape / presence / coherence; vocab membership is a warning
└─► catalog (per-tenant, URI-namespaced) ─► DiscoverResources ─► signed Offer

The feed is a transport convenience, not a third licensing model: every line is just a ResourceEntry, the same shape every other discovery path converges to. Parsing is strict — unknown keys are rejected, never silently dropped — so a typo is a loud error, not lost data.

FieldTypeRequiredMaps toWhy it exists
domainstringyesResourceEntry.domainresource origin; with path forms the canonical URI
pathstringyesResourceEntry.pathURL path of the resource
titlestringnoResourceEntry.titlehuman label
content_idstringnoResourceEntry.content_idpublisher’s stable id; keys the resource id so a re-push upserts in place
word_countintnoResourceEntry.word_countcontent metric; informs token estimation
estimated_quantityintnoResourceEntry.estimated_quantitypre-computed metering quantity (e.g. word_count × 1.32)
content_hashstringnoResourceEntry.content_hashintegrity pin of the content bytes
hash_methodstringnoResourceEntry.hash_methodalgorithm for content_hash (e.g. sha256)
sourcestringnoResourceEntry.source (IngestionSource)provenance of the metadata (INGESTION_SOURCE_CMS_API, …)
provenance_sourcestringnoResourceEntry.provenance_sourcewho supplied the metadata (audit trail)
provenance_timestampRFC3339noResourceEntry.provenance_timestampwhen it was collected / generated
licenseobjectno (yes for any REFERENCE_ONLY term)LicenseTerm.Licensegoverning license document {id, uri, uri_digest, name}
termsarray (≥1)yesResourceEntry.terms[]the licensing offers on this resource (see below)
extobjectnoResourceEntry.extfree-form extension metadata; carries fields with no typed slot (e.g. resource_mutability, previews[]) and domain-profile data
ext_criticalstring[]noResourceEntry.ext_criticalkeys in ext a consumer MUST understand (COSE crit pattern)
attestationsarraynoResourceEntry.attestations[]signed attestations {verifier, kid, attested_at, uri, claims, signature}

resource_mutability and previews[] have no typed field on ResourceEntry — they ride inside ext and are promoted to typed fields on the Offer at discovery (Offer.identity.resource_mutability, Offer.previews[]).

Term schema (one element = one LicenseTerm)

Section titled “Term schema (one element = one LicenseTerm)”
FieldTypeMaps to
semantics"enumerated" | "reference_only"LicenseTerm.semantics
functionsstring[]Restriction{kind=FUNCTION}.permitted[]
prohibited_functionsstring[]Restriction{kind=FUNCTION}.prohibited[]
user_typesstring[]Restriction{kind=USER_TYPE}.permitted[]
geosstring[]Restriction{kind=GEOGRAPHY}.permitted[]
pricing{model, unit, rate, currency}LicenseTerm.Pricing (modelfree | per_unit | flat)
quotas[{metric, limit, window}]LicenseTerm.Quota[]
obligations[{kind, trigger, scope_license, detail}]LicenseTerm.Obligation[]
scopesstring[]LicenseTerm.scopes (Biscuit-gated, e.g. subscription:premium)

Rules (enforced by protovalidate at the RPC boundary):

  • Every term MUST carry pricing — absence is not “free”, it is rejected. Use model: "free" explicitly (rate 0).
  • per_unit REQUIRES pricing.unit; flat / free carry none.
  • A reference_only term MUST carry license.uri; any license.uri REQUIRES license.uri_digest.
  • Tokens are canonical registry values or vendor:namespaced; unregistered bare tokens are a warning, not a reject.

PushResources is atomic: if any entry fails a hard validation rule, the whole submission is rejected (InvalidArgument, naming the offending entry) and nothing is persisted. The publisher fixes and resubmits the full set. Membership / lint issues are returned in PushResourcesResponse.warnings[] and do not block.

A publisher MAY place domain-profile data (e.g. CoMP) under ext. Profiles are recommendations for what keys to use, validated and rendered at discovery — see Extension Profiles. The licensing term is authoritative: for any field an offer owns (pricing above all), the Exchange renders from the selected term and shadows a conflicting ext value. So do not state pricing inside a profile ext payload; if you do, the offer carries the term’s price.

{"domain":"example.com","path":"/articles/42","title":"","content_id":"22392",
"word_count":359,"estimated_quantity":474,"content_hash":"sha256:…","hash_method":"sha256",
"source":"INGESTION_SOURCE_CMS_API","provenance_source":"example.com",
"provenance_timestamp":"2026-06-18T09:23:45Z",
"ext":{"resource_mutability":"RESOURCE_MUTABILITY_STATIC"},
"terms":[
{"semantics":"enumerated","functions":["ai-input"],
"pricing":{"model":"per_unit","unit":"accesses","rate":0.05,"currency":"USD"},
"obligations":[{"kind":"attribution","trigger":"on_use"}]}
]}

One resource, one priced term: an AI-input license at $0.05/access with an attribution obligation, plus content-integrity and mutability metadata that surface on the discovered Offer.