SPUR telemetry events
Clear Exchange records usage as SPUR Coalition Content Telemetry v1.0 events (Apache-2.0). Field names, event types and enum values come from the published v1.0 JSON schemas (telemetry-event.json, telemetry-event-batch.json, telemetry-session.json). Code: src/telemetry/spur.ts.
Provisional fields
Every field on this page comes from the v1.0 schemas except ext_clear_exchange, which the exchange adds to every event it stores:
"ext_clear_exchange": {
"provisional": true,
"entitlement_id": "ent_9989d9af-4391-45ba-b6a8-5aac031eb7cc",
"offer_id": "off_0f0ef321-f551-42cc-bb22-cf95b7f6be9b",
"publisher_id": "pub_fieldnote",
"supply_id": "local-catalogue",
"usage": "display_snippet"
}
PROVISIONAL. This object is the exchange's own correlation data. It is not part of the SPUR standard (which permits additional properties), and its name and contents may change. Agents do not send it; the exchange sets it.
The cross-checks against the entitlement and the licence-breach warnings described below are also the exchange's own application rules, not SPUR requirements.
Stages and who reports them
| Stage | SPUR event type | Reported by |
|---|---|---|
| retrieve | content_retrieved | The exchange, on every successful GET /v1/content/{id} |
| ground | content_grounded | The agent, via POST /v1/telemetry |
| cite | content_cited | The agent |
| present | content_presented | The agent |
| engage | content_engaged | The agent |
Telemetry references the other two formats by identifier only: license_ref is the entitlement jti, and terms_ref is the rate card ref (rate_card_id@vN).
content_retrieved (emitted by the exchange)
{
"document_type": "event",
"schema_version": "1.0",
"session_id": "52bb3cae-fe4f-4d40-8d5d-322f8c9d1c0e",
"agent_id": "my-agent",
"started_at": "2026-10-28T10:51:10.370Z",
"event": {
"id": "2dc312b9-38e9-4beb-9738-4f2135654744",
"type": "content_retrieved",
"timestamp": "2026-10-28T10:51:17.501Z",
"source_role": "index",
"content_telemetry_id": "3f2b8c1e-9a4d-4e7b-8c2a-1d5e6f7a8b9c",
"content_url": "https://fieldnote.example/energy/2026/04/heat-pumps-cold-climates-field-data",
"content_id": "fn-2026-0153",
"license_ref": "ent_9989d9af-4391-45ba-b6a8-5aac031eb7cc",
"terms_ref": "rc_fieldnote@v1",
"data": {
"media_type": "text",
"content_depth": "full",
"purpose": "inference",
"response_status": 200,
"response_bytes": 691
},
"ext_clear_exchange": { "provisional": true, "...": "as above" }
}
}
source_roleisindex, the role SPUR gives an intermediary that serves content to the agent.content_telemetry_idis the agent'sContent-Telemetry-IDrequest header when it is a UUID, otherwisenull.content_depthis alwaysfullandpurposealwaysinferencein the MVP.
Agent batches (POST /v1/telemetry)
The agent sends a SPUR event_batch with its entitlement as the Bearer token. A full example is in the API reference.
Batch fields
| Field | Rule |
|---|---|
document_type | "event_batch" |
schema_version | "1.0" |
session_id | UUID. Required unless ctx_token is given. Use the session_id from the query or accept response; a different one is accepted with a session_mismatch warning. |
ctx_token | Optional; must match ^ct_[A-Za-z0-9_-]{16,240}$. |
agent_id | Required; must equal the entitlement's sub (otherwise forbidden). |
started_at | Required date-time. Use session_started_at from the query or accept response. |
events | Non-empty array. |
Event fields
Every event needs type (one of the four agent stages) and timestamp (date-time). The exchange fills in content_id, license_ref and terms_ref from the entitlement when they are omitted; if given, content_id and license_ref must match the entitlement (otherwise forbidden). source_role must be omitted or "agent".
id: required on content_cited and content_presented (other events can refer to them). Optional on content_grounded and content_engaged, but an event without an id gets a fresh one and cannot be deduplicated, so agents that retry should always set ids. Ids must be distinct within a batch.
| Type | Required | Optional (validated) |
|---|---|---|
content_grounded | data.scope: session or turn | data.provenance: agent_fetched, agent_cached, third_party_sourced; data.chars_ingested, data.tokens_ingested (non-negative integers); data.content_hash (sha256:<64 hex>) |
content_cited | id, output_id, data.citation_type: direct_quote, paraphrase, reference, contradiction, unclassified | data.position: primary, supporting, mentioned, unclassified; data.excerpt_chars, data.excerpt_tokens (non-negative integers) |
content_presented | id, output_id, data.presentation_kind: content or source_reference; data.presentation_type (non-empty string) | citation_id (only valid on this type) |
content_engaged | presentation_id matching a content_presented id in this batch or already recorded for this entitlement (not needed when a ctx_token is present) | ctx_token (only valid on this type) |
Other fields allowed by the SPUR schema (such as turn_id or output_element_id) are stored as sent.
Licence warnings
The exchange compares the reported use with the licence and returns warnings (the batch is still accepted):
| Code | When |
|---|---|
terms.display_not_licensed | content_presented with presentation_kind: "content" under a grounding_only licence. |
terms.snippet_too_long | content_cited with excerpt_chars above max_snippet_chars under a display_snippet licence. |
session_mismatch | Batch session_id differs from the session the entitlement was bought in. |
For openly licensed content (Wikinews, Wikipedia) these warnings are advisory: the open licence governs.
Behaviour
- Atomic. Any error rejects the whole batch:
422 telemetry_invalid, or403 telemetry_forbiddenwhen any error is a mismatch with the entitlement.detailslists every{path, code, message}. - Idempotent per event id, per entitlement. Already-recorded ids are returned in
duplicate_event_idsand not counted again; a fully replayed batch returns202withaccepted: 0. The set of seen ids is rebuilt from the stored telemetry at startup, so a replay after a restart is still a duplicate. - Late reporting. The entitlement is accepted for
TELEMETRY_GRACE_SECONDS(default 7 days) afterexp. - Storage. Each accepted event is stored as a SPUR standalone event document (
document_type: "event"), wrapped withreceived_at,emitter(exchangeoragent) andentitlement_id. Operators read them atGET /v1/admin/telemetry.
How telemetry feeds routing and ranking
Each source has a telemetry prior: the share of the four agent stages reached per paid retrieval, smoothed toward 0.5 by PRIOR_STRENGTH pseudo-observations. It nudges routing and adds PRIOR_WEIGHT × (prior − 0.5) to relevance (scoring.prior_adjustment on each offer). GET /v1/supply shows each source's stage counts and prior. See Architecture.
Not built yet
The SPUR telemetry manifest (/.well-known/content-telemetry.json) and delivery of events to publishers. See Roadmap.