Skip to main content
sync_reporting_status is an additive, opt-in Reliable Reporting extension published in 3.2.0-rc.2 and hardened in 3.2.0-rc.3. RC.1 implementations do not expose it. Sellers that implement it advertise consumer_status_task; the task stays opt-in until the active breaking-change notice window closes on October 24, 2026, and becomes required Core only in the next eligible minor after that date.
sync_reporting_status closes the operational loop between what a seller says it published and what an authenticated buyer could actually consume. The buyer calls it on the seller through ordinary AdCP task transport. Buyers do not expose a reverse task endpoint, and sellers do not advertise a separate ingestion webhook. This task records consumer status, not measurement data. A statement carries no delivery metrics, attribution results, prices, invoices, or arbitrary observations about a user. It also is not a Reconciled Billing receipt: received proves only that the buyer consumed the exact Core revision binding, not that Managed materialization evidence or billing control totals matched. Request schema: sync-reporting-status-request.json Response schema: sync-reporting-status-response.json
3.2.0-rc.2 published the task itself; 3.2.0-rc.3 published the hardening described on this page — the content_mismatch status and its mismatch_code, the stale-received grace window, obligation_counts.consumer_status_pending, issue opened_at / issue_state / external_ref, and the operations_contact / consumer_mismatch_escalation_seconds capability fields. An RC.2 seller that already emits CONSUMER_STATUS_MISMATCH must add opened_at before validating against RC.3.

Status values

Each immutable statement addresses one exact (authenticated consumer, account, delivery_config_id, delivery_config_version, report_definition_id, period) chain: obligation_missing is deliberately keyed without a seller-issued obligation ID. Requiring that ID would make the first missing report invisible again. The seller validates the period against the authenticated caller’s accepted configuration generation instead.

content_mismatch is a contract-fact disagreement, not a measurement dispute

content_mismatch is for reporting that arrived and parsed but does not match what the buyer and seller already agreed the feed would contain. The closed mismatch_code names which agreed fact is contradicted: Each of these is checkable against the accepted generation without either side consulting its own ad server. “Your impression count is 12% below mine” is not one of them: that is a measurement disagreement, settled through measurement_terms and makegood_policy under billing authority, not through this operational loop. Because the statement carries observed_revision_content_sha256, the seller knows exactly which bytes the buyer is describing. Submitting content_mismatch requires having read the revision, so it uses the same recomputed binding as received and — like received — asserts nothing about billing acceptance.

Example

The response returns one recorded, unchanged, or failed result per statement. Exact retries reuse both the batch idempotency key and immutable reporting_status_id. Reusing either identity with changed content is an idempotency conflict. When status changes, the buyer creates a new statement and names the current leaf in supersedes_reporting_status_id. For example, a later successful read supersedes revision_missing with received. The seller keeps both statements; only the new leaf is current. The seller atomically rejects a missing or stale supersession pointer, so concurrent updates cannot fork the chain. This preserves the outage instead of allowing a successful retry to erase it. Superseded history remains readable for at least the seller’s advertised status_retention_days; older expired statements may be pruned while identity reuse and stale supersession remain rejected. Ordinary authenticated rate and per-caller resource limits protect the ledger from pathological update churn.

Seller behavior

The seller:
  1. derives consumer identity from authenticated transport;
  2. validates configuration generation, report definition, and period without requiring an obligation to exist;
  3. verifies any seller snapshot ID and timestamp against the exact caller/account-scoped snapshot it issued;
  4. records the immutable status idempotently with atomic leaf supersession;
  5. exposes the caller’s history through get_reporting_status; and
  6. compares its current producer state with the buyer’s current status.
If the seller projects healthy or complete while the buyer’s current status conflicts, only that caller/account view is degraded, with a stable CONSUMER_STATUS_MISMATCH issue. The seller assigns responsible_party from the diagnosed cause: seller production or publication, buyer access/configuration, or provider failure. One buyer’s statement never changes another buyer’s view or seller-advertised reliability statistics without corroboration. A conflict is obligation_missing, revision_missing, unreadable, content_mismatch, or a received status naming an older revision after the seller publishes a newer required restatement. The last one is the explicit signal that reporting changed after the buyer already read it.

Stale received gets a grace window

A buyer whose received is made stale only by a later seller restatement did nothing wrong: it consumed exactly what the seller required at the time, and it has not yet had a chance to re-read. That single case is therefore delayed, not action_required, until the grace deadline — the created_at of the first revision that superseded the one the buyer named, plus the configuration generation’s schedule.delivery_sla, as exact elapsed time in UTC. When that SLA resolves to a zero duration, automated_recovery_window_seconds is used instead. At or after the deadline it becomes action_required. Every other conflict is action_required immediately. The issue keeps one issue_id and one opened_at across the severity change, so a buyer ages one work item rather than two. Later restatements do not restart the window — it is anchored to the first supersession — and an advertised consumer_mismatch_escalation_seconds boundary takes precedence when the two overlap. A seller therefore cannot hold a genuinely unresolved mismatch below action_required by restating on a timer.

Escalation and contacts

A seller MAY advertise consumer_mismatch_escalation_seconds alongside an operations_contact in its media_buy.reporting_delivery capability block. Once an open or acknowledged issue has been open for that long, the seller MUST emit it at action_required with a recommended_action in the contact_* family naming the diagnosed responsible_party; wait_for_retry must not survive that boundary. operations_contact is inert human-facing metadata — an operator reads it, and agents never dereference the URL or send protocol traffic to it. Its URL is constrained to the same hardened public-origin shape as the offering document URIs, so it cannot name a loopback host, an IP literal, or a credentialed link. Issues may also carry issue_state (open, acknowledged, resolved, waived) and an inert external_ref so both sides can correlate one AdCP issue with their own ticketing systems without inventing a parallel identity. external_ref uses the same restricted character class as consumer_commit_ref, so it cannot express a URL or a sentence, and a seller must not reuse one value across callers on a caller-scoped issue. Only open and acknowledged issues appear in issues[]. resolved means the underlying disagreement ended. waived is the distinct terminal disposition for an exact disagreement that you and the seller explicitly agreed off-protocol to disregard; the seller cannot waive it unilaterally. A valid waiver retires the issue and restores the underlying seller health even while your immutable causing statement remains the current unsuperseded leaf and stays readable in consumer_statuses[]. It does not make that statement seller evidence. A later statement or materially different conflict is evaluated independently. When the waiver changes health, an offered reporting.status_changed channel carries the recovery transition with no open issue IDs.

Buyer behavior

When the seller advertises consumer_status_task, a buyer using that Reliable Reporting configuration automates this task for that configuration:
  • sync a failure promptly after its bounded retry/give-up window expires;
  • sync every later state change with explicit supersession;
  • post a current status for every elapsed expected period no later than that period’s expected_at plus the seller’s advertised automated_recovery_window_seconds; and
  • ensure every elapsed expected period still has a current status before closing the reporting scope.
The deadline matters more than the scope close: a buyer that is still retrying posts revision_missing or unreadable by the deadline and supersedes it later rather than staying silent until the campaign ends. Missing buyer feedback remains unknown. It never excuses the seller’s obligation or changes the seller’s production SLA. A seller counts a missed deadline in obligation_counts.consumer_status_pending on get_reporting_status and does nothing else with it: silence raises no issue and changes no health.

Relationship to billing receipts

sync_reporting_receipts is the stronger, optional Reconciled Billing operation. It requires a Managed materialization plus independently observed row counts, control totals, and the selected verification evidence. sync_reporting_status belongs to Core, can report a seller obligation that does not exist, and says nothing about invoice approval, payable amount, dispute waiver, or settlement. Which party’s numbers govern an invoice is a separate question, answered by measurement_terms.billing_measurement — see Billing Authority. reporting-delivery-config.json reserves an authoritative_party field for unifying the two models; in 3.2 the only accepted value is seller, and a seller MUST reject consumer with UNSUPPORTED_FEATURE. Tracked in #7440.