list_account_changes returns material changes to AdCP-visible state on one
account, including changes made outside the observing buyer’s calls. Use it
after a snapshot bootstrap, after receiving account.change_recorded, or for
an account-level audit of state transitions.
Experimental. The account change feed (
list_account_changes, the account.change_recorded webhook, the account.change_feed capability block on get_adcp_capabilities, and the CURSOR_EXPIRED error code) is part of AdCP 3.2 as an experimental surface. RFC #6810 is still open, so the surface may change between 3.x releases with at least 6 weeks’ notice. Release 3.3 adds the idempotency_key echo and requires changed_paths for adcp and seller_operator records, both described below. Sellers implementing any part of it MUST declare account.change_feed in experimental_features. See experimental status for the full contract.get_adcp_capabilities.account.change_feed.supported is true and
experimental_features contains account.change_feed.
What the feed covers
For every resource type the seller advertises, a material committed change must both appear here and be reflected on its authoritative read, regardless of whether it originated through AdCP, a seller operator or system, another authorized principal, or a connected platform. The feed describes control-plane changes: creation, configuration, lifecycle, relationships, reporting corrections, and deletion markers. It excludes reads, failed operations, dry runs, exact idempotency replays, delivery counter increments, raw audience members, raw events, catalog item bodies, and webhook delivery attempts. For the complete coverage matrix and rationale, see the account change feed design spec.Request
Request schema:account/list-account-changes-request.json
Acquire a high-water checkpoint before reading several snapshots:
Response
Response schema:account/list-account-changes-response.json
Each change record names the changed resource, action, server-derived origin,
changed paths, optional revision, and an allowlisted read-only
repair.task hint. It deliberately does not include full before/after values.
resource_revision carries the resource family’s seller revision when one is
defined: revision for a media buy or account, and seller_revision for a
creative. It is the value immediately after that change, in the same JSON type
the read uses, so a buyer can tell whether a read it already holds reflects
the change; a read returning a different value reflects later changes. The seller advances that revision on every committed change from
any origin, including the changes this feed records for seller operators,
seller automation and connected platforms. Families without a seller revision
(package, creative assignment, audience, catalog, event source, property list,
product and signal as of 3.3) omit it, and the buyer repairs by rereading. A record coalesced under its media buy may carry the media buy’s revision, because the repair read is get_media_buys. Records for state the revision does not cover, such as health and impairments, omit it.
changed_paths
changed_paths lists RFC 6901 JSON Pointers to the material fields that
changed, so a buyer can decide whether a change matters before it rereads.
- When
origin.kindisadcporseller_operator, the seller processed the mutation and knows what it changed, so the record MUST carry a non-emptychanged_paths. When the whole resource was created, deleted, or purged, use the root pointer"". - When
origin.kindisseller_systemorconnected_platform,changed_pathsstays best-effort. A seller that observed a change through a source that does not report what moved omits it, and the buyer rereads the resource. - Paths are RFC 6901 pointers into the document the
repair.taskread returns for the resource. Array indices are positions at the time of the change and can shift when elements are removed or reordered, so treat a path as a hint about what to look at, not as a stable address. - The list is a cover, not an exhaustive diff. When a change touches more than
the 64 paths a record can hold, name the nearest common ancestor pointer, or
""if there is none. Do not truncate. - Origin kinds follow where the seller learned of the change:
adcpis an AdCP task call,seller_operatoris seller staff acting through the seller’s own surface,seller_systemis seller automation, andconnected_platformis a change the seller observed in an external system, whoever made it there. A seller does not relabel a change to avoid thechanged_pathsrequirement. - A
changed_pathsof""means reread the whole resource and is the only entry in that list. - Paths never carry values, and the buyer still repairs from the authoritative read rather than from the paths.
- A 3.2 seller can omit
changed_pathson these origins. Buyers tolerate that, treat it as unknown, and reread the resource.
Recognizing your own changes
Anadcp change record can echo the idempotency_key of the request that
produced it, so a buyer on a shared account can tell its own mutations from
another principal’s.
- The seller includes
idempotency_keyonly on records whoseorigin.kindisadcp, and only on records served to the principal that sent that request. Every other reader of the account sees the record with the field omitted. - Apart from that field, records are the same across readers. The seller does
not vary whether a record appears, or its
change_id,resource,action,origin.kind, orchanged_paths, by reader. Redaction the schema already allows, such asactorandorigin.connection_id, stays reader-specific. - The key is a correlation token for the sender’s own use. Compare it with the
keys the buyer sent; a match means the change came from that request. A buyer
that sees no key on an
adcprecord does not conclude another principal made the change, because a seller without the echo omits it for everyone. “The principal that sent the request” is the principal whose authenticated scope the key was evaluated under, the same identity the cursor binds to, not a particular user or credential behind it. A seller that echoes the key keeps the originating principal and key for the full retention window, which outlasts the idempotency replay window. - One request can produce several records, such as one per creative, so match
the keys you sent as a set. A key can recur outside the replay window, so read
a match against a record’s
recorded_at. A mutation that completes asynchronously keeps the original request’s key and principal. - A seller echoes the key only when the record covers exactly one originating request. A record that coalesces changes from several requests, for example package changes folded under a media buy, omits it. A record never coalesces changes across origin kinds or principals.
- A match is a dedupe hint, not a substitute for the authoritative read. The seller may have normalized the change, or a later change may have landed since, so reread unless the mutation response already carried the final state. Persist keys before sending requests, and keep them for as long as you want to recognize the record, up to the 90-day retention window.
account.change_recordednotifications never carry the originating key, because the notification is an invalidation that every subscriber on the account receives, and the key belongs to one principal’s view of the feed. Theidempotency_keyon that payload is the delivery key for one fire, never a request key. Read the record from the feed.
acc_luma_shared carries the
same change_id, resource, action, origin.kind, and changed_paths, minus
idempotency_key.
Cursor contract
Cursor order, not timestamps, defines the total order. Appends never reorder prior pages. A filtered empty page still advances across scanned nonmatching records, so persist every returned cursor. A cursor is scoped to the authenticated principal, resolved account, and normalized filters. Do not move it between credentials or filter sets. Sellers MUST reject a principal, account, or filter mismatch withINVALID_REQUEST at
cursor; applying new filters at an old position could permanently skip
history.
The cursor also binds an authorization-scope epoch. If visible resources for
the principal expand or contract, the seller returns CURSOR_EXPIRED with
details.reason: "authorization_scope_changed". Rebootstrap snapshots so
changes skipped under the old visibility cannot become an invisible gap.
Sellers retain changes for at least 90 days. An expired cursor returns
CURSOR_EXPIRED; it never silently restarts. Recovery is to obtain a new
latest checkpoint, rebuild all authoritative snapshots, then drain changes
after the checkpoint.
Race-free bootstrap
- Register
account.change_recordedthroughsync_accounts. - Obtain C0 with
starting_position: "latest". - Enumerate
list_accounts,get_media_buys,list_creatives, financials, delivery, and other advertised reads for the account. Include all lifecycle statuses and exhaust every page. - Drain changes after C0. Locally allowlist
repair.task, construct and validate the read request from the authenticated account andresourceidentity, then invoke it. Never dispatch feed-supplied task arguments. - Persist the returned cursor and repeat after each signed notification.
- Poll periodically so a missed webhook does not create a gap.
through_cursor is only a target watermark. Do not
install it without reading all intervening pages.
Invoices
A seller that listsinvoice in account.change_feed.resource_types tells the buyer about invoices through this feed. There is no separate invoice notification type; the buyer receives the signed account.change_recorded notification and drains the feed like any other resource.
Invoice records name the invoice and point at get_account_financials for repair:
recorded_at and occurred_at carry the time of the change; the invoice has no separate issue-time field. Changes to spend, credit, or payment status keep type: "account_financials" with resource_id equal to the account id.
Repair completeness. An implementation MUST NOT advertise a resource type whose current material state cannot be recovered through its named read (see the coverage matrix). A seller that lists invoice in resource_types therefore MUST return, in get_account_financials.invoices[] (overriding any seller limit on the list), each invoice that has a change record inside the retention window (account.change_feed.retention_days) whenever the request’s period overlaps that invoice’s period. A request without period MUST return every such invoice whatever its period, so a buyer repairing from a change record omits period.
Intermediaries. An intermediary that consolidates billing is the seller of record for the invoices it issues. It records them on its own feed to its buyer and does not relay upstream sellers’ invoice records.
Sellers that do not offer the experimental feed have no invoice notification; buyers keep polling get_account_financials.
Change granularity
The feed contains at least one record per independently repairable identity. Several package or assignment changes may coalesce under their media buy whenget_media_buys repairs the complete changed closure; changes to independent
creatives require separate records. Records from one operation may share
batch_id, but retain independent change_id values and notifications.
For seller-mediated mutations, snapshot and records commit atomically. For an
external platform, records commit with the seller’s ingestion of the observed
change. A pass-through authoritative read may lead the feed only within the
source’s declared stale_after_seconds freshness bound.
Shared-account example
Suppose a connected platform changescr_8421 while the buyer is idle. The
seller records the creative revision, makes it visible on list_creatives,
appends the account change, and then fires account.change_recorded. The buyer
drains from its cursor and rereads list_creatives; it does not treat the
webhook payload as the creative document.
This is the same flow for a seller operator changing a campaign budget or
another authorized buyer pausing a package. Account visibility is based on
current authorization, not on which principal created the resource.
Relationship to webhook_activity
webhook_activity[] answers whether a webhook delivery was attempted. This
feed answers which material business-state changes were recorded. A change can
exist even when a webhook is missed; one change can also have several delivery
attempts. Never use the transport log as account history.