Skip to main content
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.
The feed is optional and appears only when 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.kind is adcp or seller_operator, the seller processed the mutation and knows what it changed, so the record MUST carry a non-empty changed_paths. When the whole resource was created, deleted, or purged, use the root pointer "".
  • When origin.kind is seller_system or connected_platform, changed_paths stays 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.task read 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: adcp is an AdCP task call, seller_operator is seller staff acting through the seller’s own surface, seller_system is seller automation, and connected_platform is a change the seller observed in an external system, whoever made it there. A seller does not relabel a change to avoid the changed_paths requirement.
  • A changed_paths of "" 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_paths on these origins. Buyers tolerate that, treat it as unknown, and reread the resource.

Recognizing your own changes

An adcp 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_key only on records whose origin.kind is adcp, 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, or changed_paths, by reader. Redaction the schema already allows, such as actor and origin.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 adcp record 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_recorded notifications 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. The idempotency_key on that payload is the delivery key for one fire, never a request key. Read the record from the feed.
The same record served to any other principal on 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 with INVALID_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

  1. Register account.change_recorded through sync_accounts.
  2. Obtain C0 with starting_position: "latest".
  3. 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.
  4. Drain changes after C0. Locally allowlist repair.task, construct and validate the read request from the authenticated account and resource identity, then invoke it. Never dispatch feed-supplied task arguments.
  5. Persist the returned cursor and repeat after each signed notification.
  6. Poll periodically so a missed webhook does not create a gap.
The webhook’s optional through_cursor is only a target watermark. Do not install it without reading all intervening pages.

Invoices

A seller that lists invoice 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 when get_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 changes cr_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.