Skip to main content
Reliable Reporting is experimental in 3.2. reporting.core is its required tier; managed_delivery and reconciled_billing are separately advertised optional tiers.The consumer-status loop is additive and opt-in. It is published in 3.2.0-rc.2 and hardened in 3.2.0-rc.3, and it stays opt-in until the notice window closes on October 24, 2026. Sellers advertise consumer_status_task only when implemented. The active notice makes it required Core in the next eligible minor after that date; RC.1 and the current public training seller remain unchanged.
Every buyer eventually asks one question: “do I have definitive reporting for this period — and if not, whose problem is it?” 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:
  1. 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.
  2. A zero-row report differs from no report. An empty period commits a revision like any other; absence means something is wrong.
  3. Revisions are immutable logical content. A provisional restatement is a new snapshot revision superseding the old snapshot, never an edit. An official revision is immutable billing-purpose evidence and cannot be superseded; later corrections are explicit accounting adjustments.
  4. get_reporting_status exposes seller state — one authoritative read over the obligation ledger, summarized by five health states.
  5. When consumer_status_task is advertised, sync_reporting_status closes 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 and next_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 matching periods view 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_at as period.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 at expected_at.
For example, a configuration activated 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_status is the authoritative recovery path; a seller with no outbound push is fully conformant. When advertised, sync_reporting_status is an ordinary inbound task call, not a seller push or a buyer-hosted callback. If you do offer push, Core’s doorbell is reporting.status_changed — an invalidation for health transitions in either direction, with stable issue_ids. reporting.ledger_changed is the separate optional invalidation for every new revision or adjustment, including changes that leave health untouched. reporting.delivery_ready belongs 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 existing get_media_buy_delivery read or legacy reporting webhook remains an optional data transport. Core never requires push. Destinations, manifests, and provisioning belong to the managed_delivery tier.
  • No Managed canonicalization or billing receipts. reconciliation_mode is delivery_only, offering profiles omit the external-materialization canonicalization_* contract, and sync_reporting_receipts does not exist for you. The opt-in sync_reporting_status extension 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 the reconciled_billing tier.
The boundary is executable: the 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

  1. Advertise the tier. In get_adcp_capabilities, add media_buy.reporting_delivery with supported: true, configuration_task, status_task, at least one offering, automated_recovery_window_seconds, and status_retention_days — and add media_buy.reporting_delivery to experimental_features. Do not set managed_delivery or reconciled_billing until you implement them.
  2. 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, and reconciliation_mode: "delivery_only". No method.
  3. Generate obligations. When an account installs a delivery configuration through sync_accounts, derive its future period boundaries and next_expected_at. At each eligible period.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 matching get_reporting_status periods view must return it, even when the first report never arrives. Campaign starts, stops, and configuration generations change future obligations, never past ones.
  4. 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-adjustment record applied to an open accounting period.
  5. 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.
  6. 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, and recommended_action whenever health is delayed or action_required.
  7. Escalate honestly. A due obligation stays delayed while automated recovery runs, for at most automated_recovery_window_seconds; after that it is action_required. Never park a dead feed in delayed. A CONSUMER_STATUS_MISMATCH follows the same discipline: every conflict is action_required immediately except a received status made stale only by your own restatement, which is delayed until the first superseding revision’s created_at + delivery_sla (or automated_recovery_window_seconds when that SLA resolves to zero) and action_required after. Later restatements do not restart that window. Carry one issue_id and one opened_at across the transition. If you advertise consumer_mismatch_escalation_seconds, also advertise operations_contact and switch recommended_action to a contact_* 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 offered reporting.status_changed recovery fire when health changes.
  8. Count silence; do not act on it. A buyer that misses its expected_at + automated_recovery_window_seconds status deadline is counted in obligation_counts.consumer_status_pending and nothing else. Missing buyer status never changes your health, your reliability statistics, or another caller’s view.

Build order for a buyer

  1. 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.
  2. Derive expected periods. Calculate the same half-open intervals and expected_at timestamps. A mid-period configuration activation begins at the next full boundary; deactivation still owes a period that already started.
  3. Use your own denominator. For each elapsed expected period, require a matching obligation in the complete paginated periods view. The seller’s returned obligation list cannot prove that the first one was not omitted.
  4. Close the history. Keep one ledger_snapshot_id across 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 official reporting_revision_id as supporting evidence and apply later adjustments under its own controls. Apply managed-resource and receipt checks only when the selected tier requires them.
  5. Checkpoint repair. After consuming every page, persist changes_checkpoint; send it as changes_after on the next periods read. Deduplicate safely replayed records by immutable ID.
  6. Sync consumer status when advertised. If the seller declares consumer_status_task, call sync_reporting_status after 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 by expected_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 is content_mismatch with a closed mismatch_code, not a measurement argument. When a seller restatement makes your received stale, re-read and supersede within the grace window rather than waiting for the escalation.
  7. Treat push as a wake-up. reporting.ledger_changed starts a changes_after repair. Because a clock-only health transition may commit no ledger record, reporting.status_changed starts a non-incremental current summary or periods read. Poll periodically even when notifications are enabled.

How to verify

  • The reporting-core-declaration universal storyboard grades the proper-name Reliable Reporting 1.0 declaration and Core boundary. The capability-gated reliable-reporting-managed-delivery and reliable-reporting-reconciled-billing storyboards grade the optional tiers. The lifecycle-focused reporting-core storyboard uses reporting_core_lifecycle_probe to verify an obligation-before-report, clock-derived delayed health, and explicit zero-row publication. Sellers advertising consumer_status_task additionally run the capability-gated reporting-consumer-status storyboard 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_status and reconcile obligations against what they received.
Reliable Reporting 1.0 is published in the RC wire bundles; the consumer-status loop is published in 3.2.0-rc.2 and hardened in 3.2.0-rc.3. The public training sales agent serves whichever prerelease contract its deployment advertises, which may trail the newest published bundle. Read the advertised version from get_adcp_capabilities and put that exact negotiated value on every sync_accounts, controller, and get_reporting_status request rather than assuming the latest tag. 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.

The ladder above Core

Nothing in Core obligates you to climb, and each rung is separately advertised:
  • managed_delivery: true adds offerings with a method — file transfer, dataset shares, warehouse materialization — plus exact-resource retention and authorization-revocation bounds, bound to principal-layer destinations (sync_principal).
  • reconciled_billing: true adds receipt_task (sync_reporting_receipts), consumer_receipt reconciliation, and the canonical-digest contract — bilateral agreement on exact logical content, for billing.
Start with Core. It solves the problem everyone actually has.

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 by measurement_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_usage is 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_mismatch on sync_reporting_status covers contract facts the accepted configuration generation already fixed. It is not the channel for “your count and mine differ by 12%”; that is max_variance_percent and makegood_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 manifest files[] 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: