{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://adcontextprotocol.org/schemas/3.3.0-beta.1/media-buy/sync-catalogs-request.json",
  "title": "Sync Catalogs Request",
  "description": "Request parameters for syncing buyer-managed catalog feeds, pushing immediate item availability overrides, and reading current availability state. Supports bulk operations across multiple catalog types. Existing catalogs matched by catalog_id are updated, new ones are created. When catalogs, item_availability_updates, and item_availability_queries are all omitted, the call is discovery-only.",
  "x-tool-summary": "List or synchronize buyer-managed account catalog feeds, suppress or restore specific items, and read current availability state.",
  "type": "object",
  "allOf": [
    {
      "$ref": "https://adcontextprotocol.org/schemas/3.3.0-beta.1/core/version-envelope.json"
    },
    {
      "not": {
        "properties": {
          "dry_run": {
            "const": true
          }
        },
        "required": [
          "dry_run",
          "item_availability_updates"
        ]
      },
      "errorMessage": "dry_run cannot be combined with item_availability_updates because immediate availability acknowledgements represent applied state, not a preview"
    }
  ],
  "x-mutates-state": true,
  "properties": {
    "idempotency_key": {
      "type": "string",
      "description": "Client-generated unique key for at-most-once execution. Catalog upserts and item availability transitions can emit audit events or trigger platform work — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.",
      "minLength": 16,
      "maxLength": 255,
      "pattern": "^[A-Za-z0-9_.:-]{16,255}$"
    },
    "account": {
      "$ref": "https://adcontextprotocol.org/schemas/3.3.0-beta.1/core/account-ref.json",
      "description": "Seller account containing these buyer-managed catalogs."
    },
    "catalogs": {
      "type": "array",
      "description": "Array of catalog feeds to sync (create or update). When media_buy.features.catalog_ingestion is declared, the seller MUST reject unlisted catalog types, source modes (url means feed_url; items means inline_items), feed formats, and explicitly supplied content_id_type values outside a declared supported_content_id_types list with UNSUPPORTED_FEATURE before mutation, including dry runs. The max_inline_items_per_request limit counts items across all catalogs in this request; excess items fail with INVALID_REQUEST before mutation. Missing catalog_ingestion means details are unknown; buyers may probe with dry_run: true. Catalog references without url or items do not select an ingestion mode. Native AdCP offering feeds omit feed_format. When omitted together with item_availability_updates and item_availability_queries, the call is discovery-only and returns existing accessible catalogs on the account without modification. A seller declaring per_item reporting MUST return complete current item statuses for accessible buyer-managed catalogs on discovery, without re-ingestion or restarting review. Buyers use a fresh idempotency_key for each current read because replayed results are historical snapshots.",
      "items": {
        "$ref": "https://adcontextprotocol.org/schemas/3.3.0-beta.1/core/catalog.json"
      },
      "minItems": 1,
      "maxItems": 50
    },
    "item_availability_updates": {
      "type": "array",
      "description": "Immediate suppress or restore operations for items in buyer-managed catalogs. Sellers declaring media_buy.features.catalog_item_availability_updates MUST process these updates synchronously and MUST NOT silently ignore them or return a submitted task. A seller that does not declare the capability MUST reject the request with UNSUPPORTED_FEATURE before lookup or mutation and MUST NOT interpret it as discovery. The combined number of item_availability_updates and item_availability_queries MUST NOT exceed 1,000; excess entries are an operation-level INVALID_REQUEST before lookup or mutation. Each (catalog_id, catalog_generation, item_id) tuple MUST appear at most once in updates; a duplicate is an operation-level INVALID_REQUEST before mutation in every validation mode. For mixed catalog/update requests, the seller MUST validate and stage the entire request against the post-upsert candidate state, then commit catalog and availability changes atomically. It MUST reject before any mutation if synchronous atomic commit is unavailable. A successful suppress acknowledgement means the seller MUST stop selecting or rendering the item and every cached or pre-generated creative it materialized from the item. Seller-internal generation lineage MUST retain resolved_account_id, catalog_id, catalog_generation, and item_id. If the seller cannot enforce that guarantee, it MUST return a failed per-item result. Suppression persists across scheduled feed fetches and catalog upserts until explicit restore, expires_at, or deletion of the containing catalog. Restore removes only an existing buyer-authored overlay or tombstone in the same catalog generation and cannot override seller rejection, withdrawal, policy, rights, or inventory controls. A restore for an absent item without such prior state fails with REFERENCE_NOT_FOUND.",
      "items": {
        "$ref": "https://adcontextprotocol.org/schemas/3.3.0-beta.1/core/catalog-item-availability-update.json"
      },
      "minItems": 1,
      "maxItems": 1000
    },
    "item_availability_queries": {
      "type": "array",
      "description": "Read current buyer-authored availability state. Queries require media_buy.features.catalog_item_availability_updates; a seller that does not declare it rejects with UNSUPPORTED_FEATURE before lookup. Any request containing queries is synchronous. In a mixed request the seller validates and stages catalog upserts and availability updates first, evaluates queries against that post-upsert/post-update candidate state, and atomically commits the staged mutations before returning those query results. If the mixed work cannot commit synchronously, it rejects before mutation. The seller returns exactly one item_availability_states entry per query in the same order and echoes request_index and the complete identity. Unknown, inaccessible, stale-generation, and unauthorized references use the normalized REFERENCE_NOT_FOUND shape described by validation_mode. Use a fresh idempotency_key for a current read; a replayed response is a historical snapshot.",
      "items": {
        "$ref": "https://adcontextprotocol.org/schemas/3.3.0-beta.1/core/catalog-item-availability-ref.json"
      },
      "minItems": 1,
      "maxItems": 1000
    },
    "catalog_ids": {
      "type": "array",
      "description": "Optional filter limiting catalog sync and discovery results to these catalog IDs. Only these catalogs are created/updated or returned by a discovery-only call. Other catalogs on the account are unaffected.",
      "items": {
        "type": "string",
        "x-entity": "catalog"
      },
      "minItems": 1,
      "maxItems": 50
    },
    "delete_missing": {
      "type": "boolean",
      "default": false,
      "description": "When true, buyer-managed catalogs on the account not included in this sync will be removed. Does not affect seller-managed catalogs. Requires catalogs; item_availability_updates alone cannot define deletion scope."
    },
    "dry_run": {
      "type": "boolean",
      "default": false,
      "description": "When true, preview catalog create, update, and delete changes without applying them. MUST NOT be combined with item_availability_updates."
    },
    "validation_mode": {
      "$ref": "https://adcontextprotocol.org/schemas/3.3.0-beta.1/enums/validation-mode.json",
      "default": "strict",
      "description": "Validation strictness for semantically valid-looking catalog and item entries. In strict mode (default), an unknown, inaccessible, unauthorized, or stale-generation catalog/item reference, a known seller-managed catalog, a stale expected_overlay_revision, or another per-entry error fails the entire operation before any catalog or availability mutation. In lenient mode, the seller returns a positionally matched failed result for each such item entry and processes the remaining valid entries. Unknown, inaccessible, unauthorized, and stale-generation references MUST be observationally equivalent: code REFERENCE_NOT_FOUND, message exactly 'Catalog item not found', recovery 'correctable', and no field, suggestion, retry_after, issues, details, or resource metadata. Authorization and lookup MUST use the same externally observable failure path and SHOULD avoid materially distinguishable timing. A known seller-managed catalog may use INVALID_REQUEST only after catalog access is authorized. Request-schema failures, duplicate identity tuples, unsupported capability, batch-limit excess, dry_run conflicts, and mixed requests that cannot commit synchronously and atomically are operation-level failures before lookup or mutation in both modes. A stale revision uses CONFLICT without mutation."
    },
    "push_notification_config": {
      "$ref": "https://adcontextprotocol.org/schemas/3.3.0-beta.1/core/push-notification-config.json",
      "description": "Optional webhook configuration for async sync notifications. Publisher will send webhook when sync completes if operation takes longer than immediate response time (common for large feeds requiring platform review)."
    },
    "context": {
      "$ref": "https://adcontextprotocol.org/schemas/3.3.0-beta.1/core/context.json"
    },
    "ext": {
      "$ref": "https://adcontextprotocol.org/schemas/3.3.0-beta.1/core/ext.json"
    }
  },
  "required": [
    "idempotency_key",
    "account"
  ],
  "if": {
    "properties": {
      "delete_missing": {
        "const": true
      }
    },
    "required": [
      "delete_missing"
    ]
  },
  "then": {
    "required": [
      "catalogs"
    ],
    "errorMessage": "catalogs is required when delete_missing is true — omitting it would delete all buyer-managed catalogs on the account"
  },
  "additionalProperties": true,
  "examples": [
    {
      "description": "Sync product and inventory feeds for a retail media campaign",
      "data": {
        "idempotency_key": "a1d5c8e9-7890-489c-def0-12345678901a",
        "account": {
          "account_id": "acct_acmecorp"
        },
        "catalogs": [
          {
            "catalog_id": "product-feed",
            "name": "Acme Product Catalog",
            "type": "product",
            "url": "https://feeds.acmecorp.com/products.xml",
            "feed_format": "google_merchant_center",
            "update_frequency": "daily"
          },
          {
            "catalog_id": "inventory-feed",
            "name": "Store Inventory",
            "type": "inventory",
            "url": "https://feeds.acmecorp.com/inventory.json",
            "feed_format": "custom",
            "update_frequency": "hourly"
          }
        ]
      }
    },
    {
      "description": "Sync inline offerings for a recruitment campaign",
      "data": {
        "idempotency_key": "b2e6d9f0-8901-489d-ef01-23456789012b",
        "account": {
          "account_id": "acct_restaurants"
        },
        "catalogs": [
          {
            "catalog_id": "chef-vacancies",
            "name": "Chef Position Vacancies",
            "type": "offering",
            "items": [
              {
                "offering_id": "chef-amsterdam-42",
                "name": "Head Chef - Amsterdam",
                "description": "Lead our kitchen team in central Amsterdam",
                "landing_url": "https://jobs.acme-restaurants.com/chef-amsterdam-42",
                "geo_targets": {
                  "countries": [
                    "NL"
                  ],
                  "regions": [
                    "NL-NH"
                  ]
                }
              }
            ]
          }
        ]
      }
    },
    {
      "description": "Discovery-only: list all catalogs on the account",
      "data": {
        "idempotency_key": "c3f7e0a1-9012-489e-f012-34567890123c",
        "account": {
          "account_id": "acct_acmecorp"
        }
      }
    },
    {
      "description": "Immediately suppress an out-of-stock item and restore another item without re-sending either catalog feed.",
      "data": {
        "idempotency_key": "d4a8f1b2-0123-489f-a123-45678901234d",
        "account": {
          "account_id": "acct_acmecorp"
        },
        "item_availability_updates": [
          {
            "catalog_id": "product-feed",
            "catalog_generation": "catgen_01K2ABCD",
            "item_id": "SKU-12345",
            "expected_overlay_revision": 0,
            "action": "suppress",
            "reason": "out_of_stock"
          },
          {
            "catalog_id": "product-feed",
            "catalog_generation": "catgen_01K2ABCD",
            "item_id": "SKU-67890",
            "expected_overlay_revision": 3,
            "action": "restore",
            "reason": "back_in_stock"
          }
        ]
      }
    }
  ]
}
