Skip to content

Proposal: catalog_version token for conditional catalog fetches (ETag-style) #4761

@bokelley

Description

@bokelley

One of three companion v3.1 proposals for catalog sync between AdCP agents and consumers (storefronts, federated marketplaces, registries). The other two address #4762 (get_signals wholesale mode) and a per-agent #4763 (inventory change-feed) — issues to follow.

Problem

Even with get_products buying_mode: "wholesale" (and the proposed get_signals discovery_mode: "wholesale"), a consumer mirroring an agent's catalog must re-fetch the full paginated result on every poll cycle to detect changes. For agents whose catalogs are stable for hours or days, this wastes consumer bandwidth and seller compute.

A separate proposal (per-agent inventory change-feed) solves this more comprehensively for agents that implement it. But that proposal will have a long adoption tail. Those consumers still want a cheap "has anything changed?" probe.

The web solved this in 1999 with HTTP ETag / If-None-Match. The protocol-level equivalent is a catalog_version token: opaque, agent-defined, returned on every wholesale response, and submittable on subsequent requests so the agent can short-circuit with "no changes."

This is the cheapest of the three companion proposals to spec and the lowest implementation cost for sellers — yet it removes the majority of wasted polling traffic for stable catalogs. It is independent of, and complementary to, the change-feed proposal: agents MAY implement either, both, or neither.

Goals

  1. A consumer that just synced an agent's catalog can ask "has anything changed since version X?" in one cheap call, regardless of catalog size.
  2. The mechanism works for get_products (any buying_mode) and get_signals (any discovery_mode), not only wholesale enumeration. Brief-mode results MAY also carry a version so consumers can cache.
  3. The token is opaque to consumers — agents choose their own versioning scheme (monotonic counter, content hash, commit SHA, etc.).
  4. Backward-compatible: pre-v3.1 agents ignore the token; consumers see no version returned and fall through to full re-fetch.

Design Principles

  • Opaque tokens. Consumers MUST treat catalog_version as opaque. No format, no ordering, no inspection.
  • Cheap unchanged response. When the agent confirms "no change," the response carries no products[]/signals[] payload — just the metadata confirming the version is still current.
  • No new endpoint. This works on the existing get_products / get_signals tasks. Adding parameters and response fields, not surfaces.
  • Two-level versioning (optional). A separate pricing_version lets agents tell consumers "structure is unchanged but pricing moved" — common for rate-card updates that don't change inventory metadata.

Request Shape

Add optional fields to get_products and get_signals:

Parameter Type Required Description
if_catalog_version string No An opaque catalog_version token returned by a prior response from this agent. When provided, the agent compares against its current catalog version and MAY return an unchanged: true response (omitting products/signals) if nothing has changed.
if_pricing_version string No An opaque pricing_version token from a prior response. Same behavior, narrower scope: structure must be unchanged AND pricing must be unchanged. Optional — agents that don't track pricing separately ignore this and fall back to if_catalog_version semantics.

Both fields are scoped to the (agent, caller_account, filters) tuple. Versions are NOT comparable across agents, across accounts, or across different filter sets. Consumers MUST cache the version returned alongside the request parameters used.

Response Shape

Add response metadata fields:

Field Type Description
catalog_version string Opaque token representing the version of the catalog state used to compose this response. Agents implementing this proposal MUST return it on every response. Agents not implementing it MAY omit.
pricing_version string Optional opaque token representing the version of the pricing layer. When the agent supports independent pricing versioning, pricing_version changes when prices move but catalog_version changes only when structure/metadata moves. Agents not separating these MAY omit pricing_version and use catalog_version for both.
unchanged boolean true ONLY when the request carried if_catalog_version (and/or if_pricing_version) matching the agent's current version. When true, products[]/signals[] MUST be omitted; catalog_version (echoed) MUST still be present.

Unchanged response example

Request:

{
  "buying_mode": "wholesale",
  "if_catalog_version": "v2026-05-18T08:00:00Z-acme-rev412"
}

Response (catalog unchanged):

{
  "message": "Catalog unchanged since v2026-05-18T08:00:00Z-acme-rev412.",
  "context_id": "ctx-abc-789",
  "unchanged": true,
  "catalog_version": "v2026-05-18T08:00:00Z-acme-rev412",
  "pricing_version": "v2026-05-18T08:00:00Z-acme-rev412"
}

Response (catalog changed):

{
  "message": "Returning 312 products.",
  "context_id": "ctx-abc-790",
  "unchanged": false,
  "catalog_version": "v2026-05-18T10:15:00Z-acme-rev415",
  "pricing_version": "v2026-05-18T10:15:00Z-acme-rev415",
  "products": [ ... ],
  "pagination": { "has_more": true, "cursor": "..." }
}

Consumers receiving unchanged: true MUST NOT mutate their local catalog mirror.

Versioning Scope

A returned catalog_version is scoped to the request parameters that produced it. The agent MUST return a version that is sensitive to: buying_mode / discovery_mode, account, filters, property_list / catalog.

The simplest correct implementation is: hash the request parameters with the current catalog state. Sophisticated agents may use a global version that's safe to reuse across requests; naive agents may use per-request hashes. Both are conformant.

Pagination Interaction

catalog_version is associated with the catalog as a whole, not individual pages. When a consumer is mid-pagination and the catalog mutates between pages, the agent SHOULD either (1) return the new catalog_version on each page and let the consumer decide whether to restart (RECOMMENDED), or (2) snapshot the catalog at the start of pagination and serve subsequent pages from that snapshot. Consumers receiving a catalog_version change mid-pagination SHOULD restart pagination from cursor: null.

Backward Compatibility

  • Pre-v3.1 agents that ignore if_catalog_version simply return the full payload — semantically correct, just inefficient. Same as missing-ETag behavior in HTTP.
  • Pre-v3.1 consumers that don't send if_catalog_version continue to receive full payloads.
  • Agents MAY implement only the response side (always return catalog_version, never honor if_catalog_version requests) as a transitional step.

Implementation Phases

  1. Spec + reference behavior. Add catalog_version, pricing_version, unchanged to response schemas; if_catalog_version, if_pricing_version to request schemas. Update task reference docs.
  2. Reference implementation in the AdCP reference sales agent + signals agent. Conformance test vector: "GET with matching version returns unchanged: true and no payload."
  3. Client SDK helpers@adcp/client caches the most recent catalog_version per (agent, account, filters) tuple and automatically sends if_catalog_version on subsequent identical requests.

Open Questions

  1. Should unchanged be an HTTP-level signal (304 Not Modified) instead of a body field? AdCP runs over MCP and A2A, not HTTP semantics. A protocol-level field is more portable. Recommendation: body field.
  2. TTL on versions. Should the spec require a minimum retention for version recognition? Recommendation: no minimum; agents that can't recognize the token return full payload, same as the unchanged-server path.
  3. pricing_version necessity. A simpler proposal collapses pricing into catalog_version. Recommendation: keep pricing_version as optional. Many sellers update prices far more often than structure, and the separation lets storefronts re-price compositions without re-rendering catalogs.

Happy to follow up with a PR adding this to specs/ and updating the get_products / get_signals task references if maintainers are aligned on the direction. Reference implementation will land in the prebid salesagent as part of our v3.1 conformance prep.

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions