Documentation Index
Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
Use this file to discover all available pages before exploring further.
Snapshot and log
Every state surface AdCP exposes has two faces: a snapshot read from aget_* task and a log of push events fired against a registered webhook URL. The snapshot says what is true now. The log says what fired, when, with what id. This page is the contract that keeps them coherent.
You don’t need to read this page to call an AdCP task. You do need to read it to build a webhook receiver, to propose a new notification type, or to argue that a missing-event scenario is a spec gap rather than a buyer-side bug.
The two faces
Snapshot
The current truth, exposed on a read API:get_media_buysreturns each buy’sstatus,health, openimpairments[], andwebhook_activity[].list_creativesreturns each creative’sstatus.sync_audiences(without changes) returns each audience’s currentstatus.get_event_source_healthreturns each source’s currentassessment-status.
Log
A stream of push events fired to the buyer’s registered webhook URL:- Delivery report fires (
notification_type: scheduled | final | delayed | adjusted). - Dependency impairment fires (
notification_type: impairment). - Future event types are added the same way: a new
notification-typevalue, a defined payload, the same delivery contract.
notification_id and corresponds to a change visible on the snapshot.
The five rules
These rules apply across every snapshot/log pair in the protocol. If you’re building a new notification type, your design must satisfy all five.1. Two distinct ids: per-fire and per-state
Dedupe transport retries byidempotency_key. Correlate fires to state by notification_id. These are different ids on the same fire — receivers MUST track both.
idempotency_key— transport-layer, per delivery attempt. Issued by the seller for each fire. Receivers dedupe on this to suppress retries of the same logical fire. Defined in the webhooks transport contract.notification_id— event-layer, per state event. Stable across re-emissions of the same logical event. For state-shaped events this equals the resource’s stable id (e.g.,impairment_idis thenotification_idfor impairment events). Typed as a first-class envelope field via #4594 in 3.1; until that lands, surfaced in webhook payloads but not in the envelope schema.
idempotency_key twice is observing a transport retry — uninteresting, dedupe and move on. A receiver seeing the same notification_id twice under different idempotency_keys is observing a re-emission — signal. The seller is repeating itself, usually because the buyer’s receiver was unreachable for long enough that the seller wants to make sure the state was delivered. That’s a missed-events warning the receiver should not collapse.
For state-shaped events (impairment, lifecycle), the per-state id is the resource id. For point-in-time data events (delivery report fires), there is no persistent state id — the per-fire idempotency_key is all there is. That asymmetry is honest about the limits of Rule 4 below.
2. Every push event corresponds to a snapshot delta
There is no webhook-only state. If a webhook fires withnotification_type: impairment, the affected media buy’s impairments[] will show the impairment on the next read. If a delivery report fires, the next get_media_buy_delivery reflects the same reporting window. Push channels do not carry information unavailable from the read API.
This rule rules out push events that exist solely as ephemeral signals — “you might want to know X” without a corresponding readable state. If you want to surface a suggestion that doesn’t change state, build a pull tool, not a webhook.
3. Push is at-least-once; the snapshot is authoritative
When push and snapshot disagree, the snapshot wins. A duplicate webhook fire (samenotification_id) is the expected behavior under at-least-once delivery — buyer agents dedupe and continue. A stale webhook fire (the push reports a state that the snapshot no longer reflects, because the resource moved on) is also expected — buyer agents re-read the snapshot rather than acting on the push payload.
This is why receivers MUST verify against the snapshot before taking irreversible action on a push.
4. Either path is complete
A buyer using webhooks reliably gets all the data. A buyer using only GET (no webhooks) gets the same data. The two paths are at parity in content and granularity; the buyer chooses based on latency, ergonomics, and receiver infrastructure. This rule has two halves:- For state events (impairment, lifecycle, status changes): GET returns current state. A buyer who missed a webhook calls
get_*and reads the snapshot — recovery is lossless. ✅ Holds today. - For data-bearing events (delivery report fires, individual log events): GET SHOULD support the same windowing and granularity the webhook delivers. A seller that emits hourly
reporting_webhookfires SHOULD support hourly windowed pulls onget_media_buy_deliveryso a buyer can reconstruct identical data by polling. ⚠️ Partial today —get_media_buy_deliverysupports date-range pulls and daily breakdowns, but not arbitrary per-window granularity matching webhook frequency. #4590 closes this for delivery reporting in 3.1; once it lands and is broadly adopted, SHOULD promotes to MUST for the granularities a seller declares inwindowed_pull_granularities.
5. Push events and log entries share an id space
A webhook delivery surfaced viawebhook_activity[] references the same notification_id that the buyer received in the push body. A buyer can correlate “I received fire X” with “the seller’s log shows fire X” without bookkeeping across two namespaces. Likewise, an impairment_id referenced in impairments[] matches the notification_id of the push that announced it.
What this rules out
- A push channel for suggestions that don’t change state. If “the seller wants you to know X” doesn’t correspond to a readable field, it’s not a snapshot/log event. Build a pull tool instead. (See the advisory epic.)
- A replay tool that re-fires past webhooks. Snapshot reads are the replay. A replay tool is an operator-side debug feature; it’s not part of the buyer-facing protocol contract.
- Per-event subscription filtering. Subscription is per-resource (or per-account, for resources that outlive a buy). A buyer who registers
push_notification_configon a media buy receives every event type fired against that buy. Filtering at the receiver is fine; filtering at the protocol surface is out of scope. - A “did you receive my webhook?” confirmation step. Receivers acknowledge via HTTP 2xx; senders retry on non-2xx per the persistent webhook contract. Sellers do not poll buyers for receipt.
Where the surface doesn’t yet follow this
- Delivery reports (
scheduled/final/delayed/adjusted) predate this contract. Two related gaps close in 3.1:- Per-window data parity —
get_media_buy_deliverytoday returns aggregates and daily breakdowns but cannot reconstruct the per-window slices (hourly, monthly) thatreporting_webhookdelivers. #4590 adds windowed pulls so the GET path matches the webhook payload at the same granularity. - Per-fire transport log — even with per-window parity, buyers debugging webhook delivery want to see which fires hit their endpoint and when. The
webhook_activity[]surface onget_media_buys(#4278) closes this for transport-layer observability.
- Per-window data parity —
- Resource lifecycle webhooks beyond media-buy scope (creative state changes, audience suspensions outside a buy’s scope) are in progress under the creative-lifecycle webhooks RFC (#2261). Until those land, the snapshot half (a fresh
list_creativesorsync_audiencescall) is the only reliable signal for changes to resources not currently referenced by an active buy.
When you’d be right to push back
This section is non-normative. It describes when raising an exception is reasonable, not when one is sanctioned.
Related
- Push notifications — the transport contract that this page sits on top of.
- Media buy lifecycle — applies snapshot/log to
status+health+impairments[].