CastHQ specChange envelopeConditionsConfidenceReconciliation

Change envelope v1

Every change to an event (its date moved, its status changed, it was listed, a party disputed a fact, CastHQ's confidence in it moved) is one change envelope: a CloudEvents 1.0 structured-mode event. The change feed (GET /v1/changes?format=cloudevents) and every subscription firing carry it. Schema: /v1/change-envelope.json.

{
  "specversion": "1.0",
  "id": "chg_48213",
  "source": "https://casthq.co",
  "type": "co.casthq.event.schedule.moved_later",
  "subject": "urn:casthq:event:01a022a6-a46a-71b2-80ee-62afe5707562",
  "time": "2026-10-30T14:02:11.000Z",
  "casthqversion": "2026-08-21",
  "datacontenttype": "application/json",
  "data": {
    "cursor": "chg_48213",
    "urn": "urn:casthq:change:chg_48213",
    "event": { "id": "01a0…", "urn": "urn:casthq:event:01a0…", "url": "https://casthq.co/apple/wwdc-2027", "title": "WWDC 2027", "status": "scheduled", "state": "scheduled" },
    "kind": "schedule",
    "semantic": "moved_later",
    "old": { "start": { "precision": "day", "not_before": "…", "not_after": "…" } },
    "new": { "start": { "precision": "day", "not_before": "…", "not_after": "…" } },
    "delta": { "start_seconds": 604800, "end_seconds": null },
    "provenance": { "origin": "producer", "claim": "urn:casthq:claim:01a0…", "source": null, "source_url": "https://…", "evidence_sha256": "9f2c…", "via": "api_key" },
    "cause": null,
    "confidence": null,
    "before": { "status": "scheduled", "phase": "upcoming", "state": "date_confirmed", "listed": true, "precision": "day", "facts": { "schedule": "confirmed" }, "fact_sources": { "schedule": 2, "status": 0, "location": 0 } },
    "after":  { "status": "scheduled", "phase": "upcoming", "state": "date_confirmed", "listed": true, "precision": "day", "facts": { "schedule": "confirmed" }, "fact_sources": { "schedule": 2, "status": 0, "location": 0 } }
  },
  "proof": "eyJhbGciOiJFZERTQSIsImtpZCI6Ii4uLiJ9..Qm9n…"
}

Attributes

Attribute Meaning
id The change id, also the feed cursor. Deduplicate on (source, id)
source The deployment's web origin, https://casthq.co in production
type co.casthq.event.<kind>[.<semantic or new value>], see below. Prefix-match it: co.casthq.event.schedule. is every date move
subject The event's URN
time When the change was recorded
casthqversion The API version the payload's shape follows
proof CastHQ's Ed25519 signature: a detached JWS over the envelope's RFC 8785 canonical JSON without proof. Keys: /.well-known/casthq-keys.json
data.event The event as it is now. For the state at the change, read before / after. On an event variant also variant (its value, {"region": "euw"}, fixed for life) and variant_of (its parent); null otherwise. On an occurrence of a series also series: { id, occurrence_key, detached, off_rule } (its series, its key, fixed for life, the facts it holds for itself, and whether the rule still generates it); null otherwise
data.kind, data.semantic What changed. semantic classifies schedule moves and dispute changes
data.old, data.new The values either side
data.delta Schedule changes: how far each side moved, in seconds (positive = later). Null otherwise
data.provenance The winning claim: its origin (producer, observed, admin), claim URN, source, source_url, evidence_sha256 and via. Never a credential
data.cause The chg_ id of the change that caused this one, or null
data.confidence A confidence change only: {fact, before, after, reasons} (see Confidence). The same level either side means a claim moved only the source count
data.approval On an approval's changes only (absent otherwise): {decision, decided_by_name, note}, the decision now (approved, rejected, or null while it waits). Only the approval's namespace's readers receive it
data.before, data.after The event's derived state on either side of the change, as it was then: status, phase, state, listed, precision, a confidence level per fact (facts), per fact how many independent sources agree with the shown value (fact_sources, a count, not a confidence; absent on older changes), ticket availability (null when no source has said; absent on older changes), and lineup_urns, the topics the line-up names (empty when none; absent on older changes). before is null for created. Conditions are judged against these

Types

Kind Types
schedule .schedule.scheduled, .moved_earlier, .moved_later, .narrowed, .widened, .undated
status .status.<new status>: announced, scheduled, live, completed, cancelled, postponed, delayed
published .published.public, .unlisted, .private; .published.ready, .unready
title .title, or .title.slug when the slug was renamed
dispute .dispute.opened, .dispute.resolved
phase .phase.started, .ended, .upcoming: the clock passing the event's start or end
confidence .confidence.<level after>
availability .availability.<new value>: not_on_sale, on_sale, few_left, sold_out, waitlist, unavailable. old / new are {availability, on_sale_at?, on_sale_precision?, url?}
lineup .lineup: who takes part moved. old / new are {lineup: [{name, role?, urn?}]} (old null for the first)
recurrence .recurrence: a series' rule moved; old and new are { recurrence: <rule> | null } (null: a one-off)
variant .variant, on a variant parent when it gains a variant: semantic added, new is { action, variant, value, key } with action added or adopted
created, location, links, merged .<kind>

Two kinds are reserved and not written yet: dependency (an event's date derived from another's) and expectation. Their types will be the bare .<kind> until they are.

The set is open: new kinds appear within a version. Ignore types you do not know.

Lifecycle state

state is one value for where an event is. The rules are tried in order; the first that holds wins:

# State Rule
1 cancelled status cancelled
2 postponed status postponed
3 delayed status delayed: held, timing unknown, expected to resume (a rain delay, a launch hold)
4 overdue a commitment (kind commitment, in a private namespace) whose deadline has passed and which is not completed
5 ended status completed, or the clock has passed the end
6 released rule 7 holds and the event is an instant (a game or product release)
7 live status live, or the clock has passed the start (a commitment: status live only)
8 date_confirmed the start is known to the day or finer, and its confidence is confirmed
9 scheduled the start is known to the day or finer
10 date_expected dated, but coarser than a day (a month, a quarter)
11 announced everything else: no date yet

delayed is only ever asserted by a source or the producer; CastHQ never infers it from a start time that passed. A delay does not move the schedule, so a held launch past its start is delayed with phase started. To infer a delay for yourself, wait on {"all": [{"time": {"anchor": "start", "offset": "PT0S"}}, {"not": {"status": {"in": ["live", "delayed"]}}}]}.

Ids: URNs

Every event, topic, change and claim has a URN that never changes: urn:casthq:event:<id>, urn:casthq:topic:<id>, urn:casthq:change:chg_<n>, urn:casthq:claim:<id>. It resolves at https://casthq.co/id/<urn>:

Schema: /v1/urn.json.