reporting.core makes
that question machine-answerable. Core is the required operational reporting
baseline: a seller must retain immutable revision evidence and expose its exact
binding over existing reporting transports. A seller that advertises the
consumer-status extension additionally records what the authenticated buyer
could actually consume. Core requires no destination, manifest, external
materialization, billing receipt, bespoke webhook, or outbound push code. It does require the
fixed revision-binding digest over canonical rows; this is distinct from the
Managed Delivery external-materialization canonicalization contract. Core carries
the operational cost of durable obligations, revision retention, and repair.
For an exact Core revision read, call get_media_buy_delivery with
reporting_revision_id and, optionally, an account selector. The returned reporting_revision_binding
must echo that ID and bind { reporting_revision_id, row_count, control_totals, reporting_rows } with RFC 8785 JCS and SHA-256. Exact reads are cursor-paginated:
every page repeats identical revision metadata and binding, and a consumer hashes
the concatenated rows in cursor order only after exhausting the frozen walk. Do
not substitute a fresh date-range pull for this read: it may have changed since
the immutable revision was published.
What Core is
Four Core ideas, plus one opt-in status loop:- Obligations exist before reports. For every active delivery configuration and period, the seller records what should exist — so a missing first report is detectable, not silent.
- A zero-row report differs from no report. An empty period commits a revision like any other; absence means something is wrong.
- Revisions are immutable logical content. A provisional restatement is a
new snapshot revision superseding the old snapshot, never an edit. An
officialrevision is immutable billing-purpose evidence and cannot be superseded; later corrections are explicit accounting adjustments. get_reporting_statusexposes seller state — one authoritative read over the obligation ledger, summarized by five health states.- When
consumer_status_taskis advertised,sync_reporting_statuscloses the loop — the buyer tells the seller whether an expected period was received, omitted, missing, unreadable, or contradictory. Both attributed histories stay visible; neither overwrites the other.
Lifecycle at a glance
The reporting configuration creates the clock. Accepting a media buy does not start a separate reporting SLA. Before a period closes, both sides can derive its boundary andnext_expected_at, but the seller cannot freeze an
all_media_buys denominator yet. At the period boundary, the obligation
becomes part of the authoritative ledger whether or not source data exists.
A closed, complete summary can still have an active configuration schedule.
The seller MUST populate next_expected_at with the nearest future period
start across its active in-scope delivery_config_generations when such a
period exists outside the evaluated scope, and omit it when none exists.
This summary projection preserves both complete-health scope guards and MUST
NOT create, expose, lease, count, or alter an open-period obligation or change
the scope’s coverage. See
future expectations on complete summaries
for the selection rule and its distinction from obligation and webhook deadlines.
This gives each side a distinct responsibility:
- Seller: commit every elapsed eligible period before taking a ledger
snapshot after
period.end, independently of source availability and before committing a revision. A matchingperiodsview includes it in the paginated record set. - Buyer: retain the accepted configuration generation, derive the same
expected periods, and treat a missing obligation as a protocol failure rather
than evidence that no activity occurred. When the seller advertises
consumer_status_task, sync the resulting consumer status back and update it whenever consumption state changes. - Both: calculate
expected_atasperiod.end + delivery_sla. The obligation is committed at period close and appears in the first ledger snapshot strictly after it; the report becomes late only atexpected_at.
00:20Z with hourly aligned periods
starts at the next full boundary. Its first period is [01:00Z, 02:00Z). The
obligation appears in the first ledger snapshot strictly after 02:00Z; with
delivery_sla: PT1H, it stays waiting through 03:00Z and becomes
delayed after that if no revision has been produced. A snapshot exactly at
02:00Z does not expose it.
What Core is not
- No push requirement. Polling
get_reporting_statusis the authoritative recovery path; a seller with no outbound push is fully conformant. When advertised,sync_reporting_statusis an ordinary inbound task call, not a seller push or a buyer-hosted callback. If you do offer push, Core’s doorbell isreporting.status_changed— an invalidation for health transitions in either direction, with stableissue_ids.reporting.ledger_changedis the separate optional invalidation for every new revision or adjustment, including changes that leave health untouched.reporting.delivery_readybelongs to the managed-delivery tier and never fires for Core configurations. - No delivery machinery. Core offerings omit
method: the status ledger and its immutable revision metadata are required, while any existingget_media_buy_deliveryread or legacy reporting webhook remains an optional data transport. Core never requires push. Destinations, manifests, and provisioning belong to themanaged_deliverytier. - No Managed canonicalization or billing receipts.
reconciliation_modeisdelivery_only, offering profiles omit the external-materializationcanonicalization_*contract, andsync_reporting_receiptsdoes not exist for you. The opt-insync_reporting_statusextension instead echoes only the fixed revision-binding SHA-256 when reporting was received; it carries no row totals, Managed evidence, or billing acceptance. Receipts and Managed external materialization machinery belong to thereconciled_billingtier.
reporting-core fixture test in the
protocol repository implements a complete polling-only seller and asserts
its source contains none of
destination | manifest | billing receipt | readiness | webhook (the Core binding
digest is intentionally retained).
Build order for a seller
- Advertise the tier. In
get_adcp_capabilities, addmedia_buy.reporting_deliverywithsupported: true,configuration_task,configuration_tasks(3.3),status_task, at least one offering,automated_recovery_window_seconds, andstatus_retention_days— and addmedia_buy.reporting_deliverytoexperimental_features. Do not setmanaged_deliveryorreconciled_billinguntil you implement them. - Define one Core offering. Feed purpose, an immutable
report_definition_id, a content-addressed reporting profile (schema URI- SHA-256, grain, primary keys), a schedule (
period_duration,alignment,delivery_sla), supported finality, andreconciliation_mode: "delivery_only". Nomethod.
- SHA-256, grain, primary keys), a schedule (
- Generate obligations. When an account installs a delivery
configuration through
sync_accounts, derive its future period boundaries andnext_expected_at. At each eligibleperiod.end, freeze the media-buy scope and commit that obligation independently of source availability and before committing its first revision. Every later ledger snapshot must record it, and a matchingget_reporting_statusperiodsview must return it, even when the first report never arrives. Campaign starts, stops, and configuration generations change future obligations, never past ones. A principal-level reporting subscription produces the same obligations for every account it covers. - Commit revisions. When a period’s data is final enough for its
declared finality, record an immutable revision — including zero-row
periods. Snapshot restatements create superseding revisions. Official
revisions are terminal; publish any later source correction as a signed
delta in a
reporting-adjustmentrecord applied to an open accounting period. - Opt in to consumer status. When implemented, advertise
consumer_status_task: "sync_reporting_status"and accept it idempotently. Validate the expected period from the authenticated caller’s immutable configuration generation even when no seller obligation exists. Retain superseded history and expose only that caller’s statements back through the periods ledger. - Project status. Serve
get_reporting_status: summary and periods views over a consistent ledger snapshot, health computed from obligations versus revisions versus the clock and current consumer status,issues[]carrying the typed code,responsible_party, andrecommended_actionwhenever health isdelayedoraction_required. - Escalate honestly. A due obligation stays
delayedwhile automated recovery runs, for at mostautomated_recovery_window_seconds; after that it isaction_required. Never park a dead feed indelayed. ACONSUMER_STATUS_MISMATCHfollows the same discipline: every conflict isaction_requiredimmediately except areceivedstatus made stale only by your own restatement, which isdelayeduntil the first superseding revision’screated_at + delivery_sla(orautomated_recovery_window_secondswhen that SLA resolves to zero) andaction_requiredafter. Later restatements do not restart that window. Carry oneissue_idand oneopened_atacross the transition. If you advertiseconsumer_mismatch_escalation_seconds, also advertiseoperations_contactand switchrecommended_actionto acontact_*value once the window elapses; that boundary wins when it overlaps the grace window. Resolve a mismatch only when the underlying conflict ends. Waive one only after an explicit bilateral agreement covering that exact caller/account issue and causing status. A valid waiver retires the issue, restores the underlying seller health while retaining the immutable consumer statement, and triggers an offeredreporting.status_changedrecovery fire when health changes. - Count silence; do not act on it. A buyer that misses its
expected_at + automated_recovery_window_secondsstatus deadline is counted inobligation_counts.consumer_status_pendingand nothing else. Missing buyer status never changes your health, your reliability statistics, or another caller’s view.
Build order for a buyer
- Save the contract. Persist every accepted reporting configuration generation, including its schedule, activation boundary, scope, report definition, coverage requirement, and required finality. Do not reconstruct historical expectations from a seller’s current configuration.
- Derive expected periods. Calculate the same half-open intervals and
expected_attimestamps. For a reporting subscription, each visible buy and resolved offering is owed the complete periods of the offering’s schedule that start on or after the first boundary at or after the latest of the subscription version’saccepted_at, the buy’sconfirmed_at, and the account’sauthorized_at, and that overlap the buy’s flight up to its stop time (the earliest ofend_time,cancellation.canceled_at, andcompleted_at, as of each period’sscope_resolved_at). Subtract the exceptions insubscription_coverage, and readexpected_attiming from each obligation’soffering_id. A mid-period configuration activation begins at the next full boundary; deactivation still owes a period that already started. - Use your own denominator. For each elapsed expected period, require a
matching obligation in the complete paginated
periodsview. The seller’s returned obligation list cannot prove that the first one was not omitted. - Close the history. Keep one
ledger_snapshot_idacross every page, deduplicate immutable IDs, match the returned record counts, and distinguish a zero-row revision from no revision. An external billing system MAY retain an exact officialreporting_revision_idas supporting evidence and apply later adjustments under its own controls. Apply managed-resource and receipt checks only when the selected tier requires them. - Checkpoint repair. After consuming every page, persist
changes_checkpoint; send it aschanges_afteron the next periods read. Deduplicate safely replayed records by immutable ID. - Sync consumer status when advertised. If the seller declares
consumer_status_task, callsync_reporting_statusafter bounded repair with the exact configuration generation, report definition, and period. Failures are sent promptly; later state changes use a new immutable status ID that supersedes the current leaf. Post a current status for every elapsed expected period byexpected_at + automated_recovery_window_seconds— not only before closing the scope — and still have one for every elapsed period at scope close. If reporting arrives but contradicts the accepted generation (a frozen media buy with no rows, a promised metric missing, the wrong currency), that iscontent_mismatchwith a closedmismatch_code, not a measurement argument. When a seller restatement makes yourreceivedstale, re-read and supersede within the grace window rather than waiting for the escalation. - Treat push as a wake-up.
reporting.ledger_changedstarts achanges_afterrepair. Because a clock-only health transition may commit no ledger record,reporting.status_changedstarts a non-incremental current summary or periods read. Poll periodically even when notifications are enabled.
How to verify
- The
reporting-core-declarationuniversal storyboard grades the proper-name Reliable Reporting 1.0 declaration and Core boundary. The capability-gatedreliable-reporting-managed-deliveryandreliable-reporting-reconciled-billingstoryboards grade the optional tiers. The lifecycle-focusedreporting-corestoryboard usesreporting_core_lifecycle_probeto verify an obligation-before-report, clock-derived delayed health, and explicit zero-row publication. Sellers advertisingconsumer_status_taskadditionally run the capability-gatedreporting-consumer-statusstoryboard for missing-obligation identity, mismatch, and supersession. - The reporting-core fixture test is the tier boundary stated as code — copy its Core capability block and offering as your starting fixtures.
- Buyers verify the contract the same way with or without webhooks: they poll
get_reporting_statusand reconcile obligations against what they received.
"3.2". Read the
advertised version from get_adcp_capabilities and put that exact negotiated
value on every sync_accounts, controller, and get_reporting_status request.
First read the version the public sales sandbox advertises, then call
comply_test_controller with
scenario: "reporting_core_lifecycle_probe" and operation: "prepare". Use
the returned account and stable identifiers in a get_reporting_status
periods read. Then run advance_time with target_health: "delayed", read
the summary, run publish_zero_row, and read the periods view again. The
controller reference
contains the exact request shapes.
Configuring through a reporting subscription
Experimental in 3.3 under
media_buy.reporting_subscriptions.sync_accounts configures reporting one
account at a time, so every new account reports nothing until someone configures
it. A seller that lists sync_principal in configuration_tasks and advertises
media_buy.reporting_delivery.subscriptions also accepts principal-level
reporting subscriptions:
- The buyer registers its reporting destinations and one subscription per
feed_purposewithscope: { "all_authorized_accounts": true }in a singlesync_principalcall. - For each account the principal is authorized for, the seller resolves the
subscription’s selector (feed purpose, period, finality, dimensions) to
offerings that the account’s products support, and creates obligations with
delivery_config_idequal to thesubscription_id. Nothing else in Core changes: obligations, revisions, health, and consumer status work as for an account-level configuration. - The buyer computes coverage without asking. Each confirmed buy it can see
through
get_media_buys, for each resolved offering, is owed the complete periods of the offering’s published schedule that start on or after the first boundary at or after the latest of the subscription version’saccepted_at, the buy’sconfirmed_at, and the account’sauthorized_at. The periods must also overlap the buy’s flight up to its stop time (the earliest ofend_time,canceled_at, andcompleted_at), and are never clipped.get_reporting_statusreturns only the exceptions insubscription_coverage[], each removing the periods whose start falls in its[from, until)interval. Accounts whose authority the principal lost are listed inget_principalinstead. See subscription coverage.
reporting_delivery_configs entry still works during 3.x. The
caller’s own entry takes precedence over the caller’s subscriptions for that account
and feed_purpose; another principal’s entries never affect them. A subscription with
replace_account_configs: true ends the caller’s own entries by cutover, without advancing the account’s revision.
The ladder above Core
Nothing in Core obligates you to climb, and each rung is separately advertised:managed_delivery: trueadds offerings with amethod— file transfer, dataset shares, warehouse materialization — plus exact-resource retention and authorization-revocation bounds, bound to principal-layer destinations (sync_principal).reconciled_billing: trueaddsreceipt_task(sync_reporting_receipts),consumer_receiptreconciliation, and the canonical-digest contract — bilateral agreement on exact logical content, for billing.
Reporting reliability and billing authority
Reliable Reporting answers “did the report I was promised arrive, intact, on time?” It does not answer “whose number governs the invoice?” That second question is answered bymeasurement_terms.billing_measurement on the media
buy — see Billing Authority
for how a deal names the seller’s ad server, the buyer’s third-party ad server,
or a measurement vendor as the authoritative counter.
The two models are deliberately separate today, and a buyer can be green on
both surfaces while the invoice is still disputed: Reliable Reporting proves
the seller’s feed was delivered and read, not that its counts match the
authoritative party’s. Until they are unified, keep the distinction explicit:
- Reliable Reporting is the operational contract for the seller-produced feed: obligations, immutable revisions, health, and — when advertised — consumer status.
report_usageis the predecessor path for media-buy billing when the authoritative counter is the buyer or a vendor. It has no obligations, no immutable revisions, no binding digest, and no status surface. Treat it as the pre-Reliable-Reporting path for that case, not as a second reporting ledger.content_mismatchonsync_reporting_statuscovers contract facts the accepted configuration generation already fixed. It is not the channel for “your count and mine differ by 12%”; that ismax_variance_percentandmakegood_policy.
reporting-delivery-config.json reserves an authoritative_party field for
the eventual unification. In 3.2 the only accepted value is seller (the
default); a seller MUST reject consumer with UNSUPPORTED_FEATURE because no
released minor defines the buyer-deposited revision task. Reserving the field
now keeps that work additive instead of breaking the billing-feed constraints.
Tracked in #7440.
File-transfer object and version references
For Managed Delivery file transfers, every manifestfiles[] entry carries a
credential-free, destination-relative object_ref. For an S3 destination,
that means the decoded object key relative to the configured destination — not
an s3:// URI and not a key with ?versionId= appended. The object key has a
1,024-character schema bound, which accommodates S3’s 1,024-UTF-8-byte key
limit; JSON Schema measures characters, not bytes.
When a provider has an immutable object version, place its decoded native
value in the entry’s optional native_version_ref, separately from the key.
This covers S3 VersionIds as well as GCS generations and provider-native
snapshots. A VersionId can include characters such as +, /, and = that
expand when URI encoded. Keep the decoded value in the manifest and encode it
only while constructing the provider request. The same destination-relative
object_ref is used by physical_checksums[], so it identifies the same
object without carrying credentials, signed URLs, or query parameters.
reporting-file-manifest.json contains the files[] entries:
reporting-verification.json contains the physical_checksums[] entries: