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
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:- derives consumer identity from authenticated transport;
- validates configuration generation, report definition, and period without requiring an obligation to exist;
- verifies any seller snapshot ID and timestamp against the exact caller/account-scoped snapshot it issued;
- records the immutable status idempotently with atomic leaf supersession;
- exposes the caller’s history through
get_reporting_status; and - compares its current producer state with the buyer’s current status.
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 advertiseconsumer_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 advertisesconsumer_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_atplus the seller’s advertisedautomated_recovery_window_seconds; and - ensure every elapsed expected period still has a current status before closing the reporting scope.
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.