From 28d1ed3f5c564344df055e0117adfb0d6de2a856 Mon Sep 17 00:00:00 2001 From: numaras Date: Tue, 15 Sep 2026 15:07:21 -0300 Subject: [PATCH 1/5] docs: regenerate openapi.json from the app The committed OpenAPI document had fallen behind the application. Regenerated with scripts/generate_openapi.py; no code change. What the regeneration picks up: - the operator key route POST /auth/api-keys/operator and its request schema (IABTechLab/seller-agent#59) - operator-auth header parameters on 17 admin paths (IABTechLab/seller-agent#39) - description-only updates on 7 paths, several of them buyer-facing: quotes, deals, change requests, negotiation messages, order transition - the required idempotency_key on change-request creation (IABTechLab/seller-agent#64) and on quotes (IABTechLab/seller-agent#50) - exclusiveMinimum on RateCardEntry.base_cpm (IABTechLab/seller-agent#78) --- docs/api/openapi.json | 866 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 825 insertions(+), 41 deletions(-) diff --git a/docs/api/openapi.json b/docs/api/openapi.json index 78194fde..e8e164aa 100644 --- a/docs/api/openapi.json +++ b/docs/api/openapi.json @@ -120,6 +120,38 @@ "default": 50, "title": "Limit" } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } } ], "responses": { @@ -161,6 +193,38 @@ "type": "string", "title": "Event Id" } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } } ], "responses": { @@ -186,6 +250,78 @@ } }, "/auth/api-keys": { + "post": { + "tags": [ + "Authentication" + ], + "summary": "Create Api Key", + "description": "Create a new BUYER-role API key for a buyer identity.\n\nRequires an operator credential. Operator keys are created via\n``POST /auth/api-keys/operator``.\n\nThe response contains the full API key which is shown ONLY ONCE.\nStore it securely \u2014 it cannot be retrieved again.", + "operationId": "create_api_key_auth_api_keys_post", + "parameters": [ + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateApiKeyRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, "get": { "tags": [ "Authentication" @@ -193,6 +329,40 @@ "summary": "List Api Keys", "description": "List all API keys (metadata only, no secrets).", "operationId": "list_api_keys_auth_api_keys_get", + "parameters": [ + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "responses": { "200": { "description": "Successful Response", @@ -201,25 +371,71 @@ "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } } - }, + } + }, + "/auth/api-keys/operator": { "post": { "tags": [ "Authentication" ], - "summary": "Create Api Key", - "description": "Create a new API key for a buyer.\n\nThe response contains the full API key which is shown ONLY ONCE.\nStore it securely \u2014 it cannot be retrieved again.", - "operationId": "create_api_key_auth_api_keys_post", + "summary": "Create Operator Api Key", + "description": "Create a new OPERATOR-role API key (no buyer identity).\n\nRequires an existing operator credential. Bootstrap the FIRST\noperator key with ``ad-seller create-operator-key`` (writes directly\nto storage, no network surface).\n\nThe response contains the full API key which is shown ONLY ONCE.\nStore it securely \u2014 it cannot be retrieved again.", + "operationId": "create_operator_api_key_auth_api_keys_operator_post", + "parameters": [ + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateApiKeyRequest" + "$ref": "#/components/schemas/CreateOperatorApiKeyRequest" } } - }, - "required": true + } }, "responses": { "200": { @@ -260,6 +476,38 @@ "type": "string", "title": "Key Id" } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } } ], "responses": { @@ -299,6 +547,38 @@ "type": "string", "title": "Key Id" } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } } ], "responses": { @@ -351,7 +631,7 @@ "Pricing" ], "summary": "Get Rate Card", - "description": "Get the current rate card (base CPMs by inventory type).\n\nThe rate card drives floor pricing during inventory sync and\ndeal creation. Can be updated via PUT to reflect ad server rate cards.", + "description": "Get the current rate card (base CPMs by inventory type).\n\nMatching entries override the catalog product's base CPM as the\nstarting price for quotes, from-template bookings, and negotiation\nanchors (floors still apply) \u2014 see ``rate_card_service`` (issue #69).\nCan be updated via PUT to reflect ad server rate cards.\n\n``source: \"defaults\"`` means no rate card has ever been stored for\nthis seller \u2014 the entries below are generic reference values, not an\noperator-configured card, and they do NOT drive pricing (a stored\nentry has to match a product's inventory type to take effect).\n``source: \"stored\"`` is the operator's actual card.", "operationId": "get_rate_card_api_v1_rate_card_get", "responses": { "200": { @@ -362,28 +642,62 @@ } } } - } - }, - "put": { - "tags": [ - "Pricing" + } + }, + "put": { + "tags": [ + "Pricing" + ], + "summary": "Update Rate Card", + "description": "Update the rate card with current base CPMs from ad server.\n\nPublishers should update this when their ad server rate cards change.\nAn entry whose ``inventory_type`` matches a catalog product overrides\nthat product's base CPM as the starting price for quotes,\nfrom-template bookings, and negotiation anchors \u2014 the product's floor\nCPM still applies (issue #69; see ``rate_card_service``). Products\nwith no matching entry keep pricing from the catalog, unchanged.", + "operationId": "update_rate_card_api_v1_rate_card_put", + "parameters": [ + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } ], - "summary": "Update Rate Card", - "description": "Update the rate card with current base CPMs from ad server.\n\nPublishers should update this when their ad server rate cards change.\nThe pricing engine uses these values as base prices before applying\ntier discounts and volume adjustments.", - "operationId": "update_rate_card_api_v1_rate_card_put", "requestBody": { + "required": true, "content": { "application/json": { "schema": { + "type": "array", "items": { "$ref": "#/components/schemas/RateCardEntry" }, - "type": "array", "title": "Entries" } } - }, - "required": true + } }, "responses": { "200": { @@ -445,6 +759,38 @@ "default": false, "title": "Incremental" } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } } ], "responses": { @@ -978,7 +1324,7 @@ "Products" ], "summary": "Override Inventory Type", - "description": "Override the auto-detected inventory type for a product.\n\nPublishers can correct misclassified inventory types from ad server sync\nor apply custom categorization. The override persists across future syncs.", + "description": "Override the auto-detected inventory type for a product.\n\nPublishers can correct misclassified inventory types from ad server sync\nor apply custom categorization. The override persists across future syncs.\nRequires an operator credential (catalog mutation).", "operationId": "override_inventory_type_api_v1_products__product_id__inventory_type_post", "parameters": [ { @@ -1098,7 +1444,7 @@ "Products" ], "summary": "Delete Inventory Type Override", - "description": "Remove an inventory type override, reverting to auto-detected type.", + "description": "Remove an inventory type override, reverting to auto-detected type.\n\nRequires an operator credential (catalog mutation).", "operationId": "delete_inventory_type_override_api_v1_products__product_id__inventory_type_delete", "parameters": [ { @@ -1109,6 +1455,38 @@ "type": "string", "title": "Product Id" } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } } ], "responses": { @@ -1339,7 +1717,7 @@ "Negotiation" ], "summary": "Post Negotiation Message", - "description": "Canonical negotiation surface \u2014 accepts the shared NegotiationMessage.\n\nThe seller now validates the SAME message the buyer emits: a required\n``action`` enum (accept/counter/reject/final_offer) plus ``buyer_price``\nas :class:`Money`. A well-formed counter no longer 422s (the historical\nbug). Priced actions (counter/final_offer) run the seller's untouched\nnegotiation engine; accept/reject are recorded as terminal rounds off\nthe existing history.", + "description": "Canonical negotiation surface \u2014 accepts the shared NegotiationMessage.\n\nThe seller now validates the SAME message the buyer emits: a required\n``action`` enum (accept/counter/reject/final_offer) plus ``buyer_price``\nas :class:`Money`. A well-formed counter no longer 422s (the historical\nbug). Priced actions (counter/final_offer) run the seller's untouched\nnegotiation engine; accept/reject are recorded as terminal rounds off\nthe existing history.\n\n**Idempotency (FD-12):** the request carries a required\n``idempotency_key``. A replay with a key already used for an identical\nbody returns the same round without consuming another one from the\nnegotiation's ``max_rounds`` budget; the same key reused with a\ndifferent body is an ``idempotency_conflict`` (HTTP 409). The key is\nscoped per buyer, same as ``/api/v1/quotes`` and ``/api/v1/deals``.", "operationId": "post_negotiation_message_api_v1_negotiations_messages_post", "parameters": [ { @@ -1457,7 +1835,7 @@ "Deal Booking" ], "summary": "Book Deal", - "description": "Book a deal from a previously issued quote.\n\nThe seller validates the quote, generates a Deal ID, and returns\nconfirmed terms. This is the commit point \u2014 the quote becomes bound.\n\n**Wire format (proposal \u00a75.6 + \u00a76 row 14b):** the seller accepts both\naudience-plan content types --\n``application/vnd.ucp.embedding+json; v=1`` (legacy UCP carrier) and\n``application/vnd.iab.agentic-audiences+json; v=1`` (new IAB Agentic\nAudiences alias). FastAPI's body parsing is content-type-permissive, so\nboth names round-trip the same Pydantic model with no custom dependency\nneeded; the dual acceptance is exercised by\n``tests/unit/test_deal_booking_snapshot.py``.\n\n**Snapshot (proposal \u00a75.1 Step 2 + wire-format \u00a76.5):** when the request\ncarries an ``audience_plan``, the seller persists it verbatim as\n``audience_plan_snapshot`` against the deal record and returns the\nsnapshot plus a per-role ``audience_match_summary`` so the buyer can\nverify the booking. The snapshot is authoritative for the lifetime of\nthe deal -- if seller capabilities change mid-flight, the snapshot is\nhonored (see ``services/fulfillment.honor_audience_plan_snapshot``).\n\n**Forensic logging (proposal \u00a75.1 Step 2):** the\n``audience_plan_id`` hash is logged at INFO via\n``ad_seller.audience.booking``. The buyer logs the same hash on its\nside; matching entries are the cross-system anchor for dispute\nresolution.\n\n**Idempotency (FD-12):** the request carries a required\n``idempotency_key``. A replay with a key already booked returns the\nsame Deal without minting a second one (no duplicate side effect).", + "description": "Book a deal from a previously issued quote.\n\nThe seller validates the quote, generates a Deal ID, and returns\nconfirmed terms. This is the commit point \u2014 the quote becomes bound.\n\n**Wire format (proposal \u00a75.6 + \u00a76 row 14b):** the seller accepts both\naudience-plan content types --\n``application/vnd.ucp.embedding+json; v=1`` (legacy UCP carrier) and\n``application/vnd.iab.agentic-audiences+json; v=1`` (new IAB Agentic\nAudiences alias). FastAPI's body parsing is content-type-permissive, so\nboth names round-trip the same Pydantic model with no custom dependency\nneeded; the dual acceptance is exercised by\n``tests/unit/test_deal_booking_snapshot.py``.\n\n**Snapshot (proposal \u00a75.1 Step 2 + wire-format \u00a76.5):** when the request\ncarries an ``audience_plan``, the seller persists it verbatim as\n``audience_plan_snapshot`` against the deal record and returns the\nsnapshot plus a per-role ``audience_match_summary`` so the buyer can\nverify the booking. The snapshot is authoritative for the lifetime of\nthe deal -- if seller capabilities change mid-flight, the snapshot is\nhonored (see ``services/fulfillment.honor_audience_plan_snapshot``).\n\n**Forensic logging (proposal \u00a75.1 Step 2):** the\n``audience_plan_id`` hash is logged at INFO via\n``ad_seller.audience.booking``. The buyer logs the same hash on its\nside; matching entries are the cross-system anchor for dispute\nresolution.\n\n**Authentication (security fix):** every other money-moving path\n(quotes, proposals, negotiation, template booking) requires a verified\nbuyer via ``deps._verified_buyer_context``; this route was deliberately\nskipped when that wave landed (July 2026, EP-5.2) because it books at\nthe already-capped quoted price -- price integrity was covered, but the\ncaller's identity was not. An OPTIONAL key was used only to scope the\nidempotency namespace, so anyone who learned a ``quote_id`` could book\nit at another buyer's negotiated tier pricing. A verified buyer is now\nREQUIRED (401 if absent), and the caller must be the buyer the\nreferenced quote was priced for -- both by recorded identity (when the\nquote's origin is on record via ``QuoteHistoryStore``) and by tier\n(403 ``buyer_mismatch`` / ``buyer_tier_mismatch`` otherwise). A quote\npriced at the PUBLIC tier (no identity asserted at quote time) has\nnothing buyer-specific to steal, so any authenticated buyer may book it.\n\n**Idempotency (FD-12):** the request carries a required\n``idempotency_key``. A replay with a key already used for an identical\nbody returns the same Deal without minting a second one (no duplicate\nside effect); the same key reused with a different body is an\n``idempotency_conflict`` (HTTP 409). The key is scoped per buyer\n(FD-12) \u2014 the now-required API key identity takes priority, matching\nthe priority order used to resolve buyer identity elsewhere.", "operationId": "book_deal_api_v1_deals_post", "parameters": [ { @@ -1869,15 +2247,49 @@ "summary": "Push Deal To Buyers", "description": "Push a deal to one or more buyer endpoints via IAB Deals API v1.0.\n\nThe seller sends deal terms to buyer DSPs. Each buyer receives an\nHTTP POST with the full IAB Deal object and responds with acceptance status.\n\nThis is the standardized deal distribution path \u2014 alternative to\nSSP-mediated distribution (PubMatic, Index Exchange, etc.).", "operationId": "push_deal_to_buyers_api_v1_deals_push_post", + "parameters": [ + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DealPushRequest" } } - }, - "required": true + } }, "responses": { "200": { @@ -1959,15 +2371,49 @@ "summary": "Distribute Deal Via Ssp", "description": "Distribute a deal through configured SSP(s).\n\nRoutes the deal to the appropriate SSP based on routing rules\nor explicit ssp_name. The SSP handles DSP-side distribution.\n\nSupports multiple SSPs: PubMatic (MCP), Index Exchange (REST),\nMagnite (REST), or any configured SSP connector.", "operationId": "distribute_deal_via_ssp_api_v1_deals_distribute_post", + "parameters": [ + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SSPDealDistributeRequest" } } - }, - "required": true + } }, "responses": { "200": { @@ -2067,15 +2513,49 @@ "summary": "Register Curator", "description": "Register a new curator.\n\nCurators can then create deals against this publisher's inventory\nvia the /api/v1/deals/curated endpoint.", "operationId": "register_curator_api_v1_curators_post", + "parameters": [ + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CuratorRegistrationRequest" } } - }, - "required": true + } }, "responses": { "201": { @@ -3334,6 +3814,40 @@ "summary": "Create Package", "description": "Create a curated package (Layer 2).", "operationId": "create_package_packages_post", + "parameters": [ + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "requestBody": { "required": true, "content": { @@ -3493,8 +4007,40 @@ "in": "path", "required": true, "schema": { - "type": "string", - "title": "Package Id" + "type": "string", + "title": "Package Id" + } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" } } ], @@ -3547,6 +4093,38 @@ "type": "string", "title": "Package Id" } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } } ], "responses": { @@ -3579,15 +4157,49 @@ "summary": "Assemble Package", "description": "Assemble a dynamic package (Layer 3) from product IDs.\n\nProduct ids resolve catalog-first with storage fallback (issue #34) \u2014\nthe same resolution as ``POST /packages``. Zero resolution is a 422\nnaming the unresolved ids; partial resolution surfaces\n``unresolved_ids``/``warnings`` on the response.", "operationId": "assemble_package_packages_assemble_post", + "parameters": [ + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DynamicPackageRequest" } } - }, - "required": true + } }, "responses": { "200": { @@ -3619,6 +4231,40 @@ "summary": "Sync Packages", "description": "Trigger ad server inventory sync (Layer 1).", "operationId": "sync_packages_packages_sync_post", + "parameters": [ + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "responses": { "200": { "description": "Successful Response", @@ -3627,6 +4273,16 @@ "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } } } @@ -3771,6 +4427,38 @@ "type": "string", "title": "Agent Id" } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } } ], "responses": { @@ -3803,15 +4491,49 @@ "summary": "Discover Agent", "description": "Discover an agent by URL.\n\nFetches the agent's card from .well-known/agent.json, checks\nall configured registries (AAMP + extras) for verification, and\nregisters the agent locally with appropriate trust status.", "operationId": "discover_agent_registry_agents_discover_post", + "parameters": [ + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DiscoverAgentRequest" } } - }, - "required": true + } }, "responses": { "200": { @@ -3852,6 +4574,38 @@ "type": "string", "title": "Agent Id" } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } } ], "requestBody": { @@ -3892,7 +4646,7 @@ "Quotes" ], "summary": "Create Quote", - "description": "Request a non-binding price quote from the seller.\n\nThe seller evaluates the request against existing pricing rules and\nreturns a quote with pricing, terms, and availability. Quotes are\nephemeral with a 24-hour TTL \u2014 no Deal ID is created.", + "description": "Request a non-binding price quote from the seller.\n\nThe seller evaluates the request against existing pricing rules and\nreturns a quote with pricing, terms, and availability. Quotes are\nephemeral with a 24-hour TTL \u2014 no Deal ID is created.\n\n**Idempotency (FD-12):** the request carries a required\n``idempotency_key``. A replay with a key already used for an identical\nbody returns the same Quote without minting a second one; the same key\nreused with a different body is an ``idempotency_conflict`` (HTTP 409).\nThe key is scoped per buyer (FD-12) \u2014 buyer verification runs before\nthe replay lookup so a second buyer can never ride an unverified\nreplay of the first buyer's key.", "operationId": "create_quote_api_v1_quotes_post", "parameters": [ { @@ -4406,7 +5160,7 @@ "Orders" ], "summary": "Transition Order", - "description": "Transition an order to a new state.\n\nValidates the transition against the state machine rules and\nrecords the change in the audit log.", + "description": "Transition an order to a new state.\n\nValidates the transition against the state machine rules and\nrecords the change in the audit log. Requires an operator\ncredential \u2014 state transitions drive billing-relevant lifecycle.", "operationId": "transition_order_api_v1_orders__order_id__transition_post", "parameters": [ { @@ -4610,7 +5364,7 @@ "Change Requests" ], "summary": "Create Change Request", - "description": "Submit a change request for an existing order.\n\nValidates the change against the current order state, classifies\nseverity, and routes to approval if needed.", + "description": "Submit a change request for an existing order.\n\nValidates the change against the current order state, classifies\nseverity, and routes to approval if needed.\n\n**Idempotency (FD-12):** the request carries a required\n``idempotency_key``. An identical replay returns the original change\nrequest without creating a second approval; reusing the key with a\ndifferent body returns ``idempotency_conflict`` (HTTP 409). Keys are\nscoped per order and expire after 24 hours.", "operationId": "create_change_request_api_v1_change_requests_post", "parameters": [ { @@ -4851,7 +5605,7 @@ "Change Requests" ], "summary": "Review Change Request", - "description": "Approve or reject a pending change request.", + "description": "Approve or reject a pending change request.\n\nRequires an operator credential \u2014 the decision is the seller's, not\nthe requesting buyer's.", "operationId": "review_change_request_api_v1_change_requests__cr_id__review_post", "parameters": [ { @@ -4934,7 +5688,7 @@ "Change Requests" ], "summary": "Apply Change Request", - "description": "Apply an approved change request to the order.\n\nUpdates the order with the proposed values from the change request.", + "description": "Apply an approved change request to the order.\n\nUpdates the order with the proposed values from the change request.\nRequires an operator credential (order mutation).", "operationId": "apply_change_request_api_v1_change_requests__cr_id__apply_post", "parameters": [ { @@ -6073,10 +6827,15 @@ }, "type": "object", "title": "CreateApiKeyRequest", - "description": "Request to create a new API key for a buyer." + "description": "Request to create a new BUYER-role API key.\n\nOperator keys are created via POST /auth/api-keys/operator\n(CreateOperatorApiKeyRequest) \u2014 they carry no buyer identity." }, "CreateChangeRequestModel": { "properties": { + "idempotency_key": { + "type": "string", + "minLength": 1, + "title": "Idempotency Key" + }, "order_id": { "type": "string", "title": "Order Id" @@ -6118,12 +6877,36 @@ }, "type": "object", "required": [ + "idempotency_key", "order_id", "change_type" ], "title": "CreateChangeRequestModel", "description": "Request to create a change request for an order." }, + "CreateOperatorApiKeyRequest": { + "properties": { + "label": { + "type": "string", + "title": "Label", + "default": "" + }, + "expires_in_days": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Expires In Days" + } + }, + "type": "object", + "title": "CreateOperatorApiKeyRequest", + "description": "Request to create a new OPERATOR-role API key (no buyer identity)." + }, "CreateOrderRequest": { "properties": { "deal_id": { @@ -9326,6 +10109,7 @@ }, "base_cpm": { "type": "number", + "exclusiveMinimum": 0.0, "title": "Base Cpm" }, "currency": { From ce50a4a5a68518b88fc6bcd2d2c1a66f4e88a775 Mon Sep 17 00:00:00 2001 From: numaras Date: Tue, 15 Sep 2026 16:40:45 -0300 Subject: [PATCH 2/5] test: add drift guard for docs/api/openapi.json --- tests/unit/test_openapi_drift.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/unit/test_openapi_drift.py diff --git a/tests/unit/test_openapi_drift.py b/tests/unit/test_openapi_drift.py new file mode 100644 index 00000000..4023b416 --- /dev/null +++ b/tests/unit/test_openapi_drift.py @@ -0,0 +1,30 @@ +# Author: Green Mountain Systems AI Inc. +# Donated to IAB Tech Lab + +"""Drift guard for the committed OpenAPI document. + +Builds the OpenAPI schema in memory the same way ``scripts/generate_openapi.py`` +does (``app.openapi()``) and asserts it equals the committed +``docs/api/openapi.json``. Any code change that adds, removes, or reshapes a +REST endpoint or schema breaks this test until the document is regenerated with: + + python scripts/generate_openapi.py + +The comparison is on parsed JSON, so formatting alone cannot cause a false drift. +""" + +import json +from pathlib import Path + +from ad_seller.interfaces.api.main import app + +_COMMITTED = Path(__file__).resolve().parents[2] / "docs" / "api" / "openapi.json" + + +def test_openapi_json_matches_app(): + """Committed docs/api/openapi.json must equal the schema the app generates.""" + assert _COMMITTED.exists(), f"{_COMMITTED} is missing — run: python scripts/generate_openapi.py" + assert json.loads(_COMMITTED.read_text()) == app.openapi(), ( + "docs/api/openapi.json is out of date with the code.\n" + "Regenerate it with: python scripts/generate_openapi.py" + ) From 3e41d5383f7c81da278a3a86ecad2e7c86d139df Mon Sep 17 00:00:00 2001 From: numaras Date: Tue, 15 Sep 2026 16:42:01 -0300 Subject: [PATCH 3/5] docs: update hand-written endpoint counts from 87 to 88 --- README.md | 4 ++-- docs/api/overview.md | 2 +- docs/architecture/overview.md | 6 +++--- docs/getting-started/quickstart.md | 4 ++-- docs/guides/chatgpt-setup.md | 2 +- docs/index.md | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6c808ce3..cb1b142b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ The seller agent exposes four communication interfaces: |-----------|----------|----------| | **MCP** | `/mcp` (Streamable HTTP), `/mcp-sse/sse` (legacy) | Primary interface — 46 tools for Claude, ChatGPT, Codex, Cursor, and buyer agents | | **A2A** | `/a2a/{agent}/jsonrpc` | Conversational JSON-RPC 2.0 for natural language queries | -| **REST** | `/api/v1/*` | Programmatic access — 87 endpoints across 25 groups | +| **REST** | `/api/v1/*` | Programmatic access — 88 endpoints across 25 groups | | **Chat** | `/chat` | Web-based conversational interface for human buyers | > [Protocol Documentation](https://iabtechlab.github.io/seller-agent/api/mcp/) @@ -215,7 +215,7 @@ INDEX_EXCHANGE_API_URL=https://api.indexexchange.com ## API Reference -87 endpoints across 25 groups: +88 endpoints across 25 groups: | Group | Endpoints | Description | |-------|-----------|-------------| diff --git a/docs/api/overview.md b/docs/api/overview.md index dca20603..a957b946 100644 --- a/docs/api/overview.md +++ b/docs/api/overview.md @@ -1,6 +1,6 @@ # API Overview -The Ad Seller System API exposes **87 endpoints** across **25 tags**. All endpoints are served from a single FastAPI application. The complete auto-generated route inventory is in the [REST Endpoints reference](../reference/endpoints.md). +The Ad Seller System API exposes **88 endpoints** across **25 tags**. All endpoints are served from a single FastAPI application. The complete auto-generated route inventory is in the [REST Endpoints reference](../reference/endpoints.md). **Base URL:** `http://localhost:8000` **OpenAPI docs:** `http://localhost:8000/docs` diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index b7fca495..86cbcc0b 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -20,7 +20,7 @@ graph LR subgraph "Seller Agent" MCP_S["/mcp/ (Streamable HTTP)
MCP Server"] A2A_S["/a2a/seller/jsonrpc
A2A Server"] - REST_S["REST API
87 endpoints"] + REST_S["REST API
88 endpoints"] NLP[NL Processing] TOOLS[Seller Tools] FLOWS[CrewAI Flows] @@ -61,7 +61,7 @@ graph TB subgraph "Protocol Layer" MCP[MCP Server
/mcp/ Streamable HTTP] A2A[A2A Server
/a2a/seller/jsonrpc] - API[REST API
87 endpoints, 25 tags] + API[REST API
88 endpoints, 25 tags] end AUTH[Auth & API Keys] @@ -135,7 +135,7 @@ graph TB ### API Layer -**FastAPI application** with 87 endpoints across 25 OpenAPI tags. Handles HTTP routing, request validation, authentication, and response serialization. See [API Overview](../api/overview.md). +**FastAPI application** with 88 endpoints across 25 OpenAPI tags. Handles HTTP routing, request validation, authentication, and response serialization. See [API Overview](../api/overview.md). ### Authentication and Agent Registry diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 3d97b5fe..51b0f469 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -62,7 +62,7 @@ Expected response: ## Browse the API Docs -Open `http://localhost:8000/docs` in a browser for the auto-generated Swagger UI with all 87 endpoints. +Open `http://localhost:8000/docs` in a browser for the auto-generated Swagger UI with all 88 endpoints. ## First API Calls @@ -118,6 +118,6 @@ Returns a confirmed deal with a Deal ID, OpenRTB parameters, and DSP activation ## Next Steps -- [API Overview](../api/overview.md) --- see all 87 endpoints +- [API Overview](../api/overview.md) --- see all 88 endpoints - [Authentication](../api/authentication.md) --- set up API keys for authenticated access - [Buyer Agent Integration](../integration/buyer-agent.md) --- connect a buyer agent diff --git a/docs/guides/chatgpt-setup.md b/docs/guides/chatgpt-setup.md index 689f93f1..4c000fba 100644 --- a/docs/guides/chatgpt-setup.md +++ b/docs/guides/chatgpt-setup.md @@ -190,6 +190,6 @@ Base URL: https://your-publisher.example.com Auth: Authorization: Bearer sk-operator-XXXXX ``` -The seller agent exposes 87 REST endpoints. See the [API Overview](../api/overview.md). +The seller agent exposes 88 REST endpoints. See the [API Overview](../api/overview.md). For ChatGPT specifically, you can also create a **Custom GPT** with Actions pointing to the REST API's OpenAPI spec at `https://your-publisher.example.com/openapi.json`. diff --git a/docs/index.md b/docs/index.md index 6a60c290..19c77a73 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,12 +12,12 @@ Part of the IAB Tech Lab Agent Ecosystem --- see also the [Buyer Agent](https:// |----------|----------|----------| | **[MCP](api/mcp.md)** | `/mcp/` (Streamable HTTP), `/mcp-sse/sse` (legacy) | Primary interface — 46 tools for Claude, ChatGPT, Codex, Cursor, and buyer agents | | **[A2A](api/a2a.md)** | *(planned — not yet served)* | Conversational agent interactions — natural language, multi-turn | -| **[REST API](api/overview.md)** | `/api/v1/*` | Programmatic access — 87 endpoints across 25 groups | +| **[REST API](api/overview.md)** | `/api/v1/*` | Programmatic access — 88 endpoints across 25 groups | ## Key Capabilities - **46 MCP tools** for Claude, ChatGPT, Codex, Cursor, and Windsurf — interactive setup wizard + full operations -- **87 REST endpoints** across 25 categories covering the complete ad selling workflow +- **88 REST endpoints** across 25 categories covering the complete ad selling workflow - **Pluggable ad server** support — Google Ad Manager and FreeWheel (Streaming Hub + Buyer Cloud) - **Multi-SSP distribution** — PubMatic (MCP), Index Exchange (REST), Magnite (REST) with routing rules - **IAB Deals API v1.0** — standardized deal push to buyer DSPs @@ -54,7 +54,7 @@ Part of the IAB Tech Lab Agent Ecosystem --- see also the [Buyer Agent](https:// ### API Reference -- [API Overview](api/overview.md) --- all 87 endpoints grouped by tag +- [API Overview](api/overview.md) --- all 88 endpoints grouped by tag - [MCP Protocol](api/mcp.md) --- 46 MCP tools for Claude, ChatGPT, and buyer agents - [A2A Protocol](api/a2a.md) --- conversational agent-to-agent interface - [Agent Discovery](api/agent-discovery.md) --- `/.well-known/agent.json` and trust registry From 32979d930d49ff64b8254d806d2e918b6bbb8b70 Mon Sep 17 00:00:00 2001 From: numaras Date: Wed, 30 Sep 2026 09:56:10 -0300 Subject: [PATCH 4/5] docs: regenerate openapi.json and endpoint counts for the deal list route Main gained GET /api/v1/deals, so the committed document fell one operation behind and the drift test would fail. Regenerate it and move the hand-written totals from 88 to 89. The README group table also catches up: Authentication counts the operator-key route (5) and Deal Booking counts the list route (12), so the rows sum to the total. Co-Authored-By: Claude Opus 5.5 --- README.md | 8 +-- docs/api/openapi.json | 110 +++++++++++++++++++++++++++++ docs/api/overview.md | 2 +- docs/architecture/overview.md | 6 +- docs/getting-started/quickstart.md | 4 +- docs/guides/chatgpt-setup.md | 2 +- docs/index.md | 6 +- 7 files changed, 124 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index cb1b142b..9e9dff76 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ The seller agent exposes four communication interfaces: |-----------|----------|----------| | **MCP** | `/mcp` (Streamable HTTP), `/mcp-sse/sse` (legacy) | Primary interface — 46 tools for Claude, ChatGPT, Codex, Cursor, and buyer agents | | **A2A** | `/a2a/{agent}/jsonrpc` | Conversational JSON-RPC 2.0 for natural language queries | -| **REST** | `/api/v1/*` | Programmatic access — 88 endpoints across 25 groups | +| **REST** | `/api/v1/*` | Programmatic access — 89 endpoints across 25 groups | | **Chat** | `/chat` | Web-based conversational interface for human buyers | > [Protocol Documentation](https://iabtechlab.github.io/seller-agent/api/mcp/) @@ -215,7 +215,7 @@ INDEX_EXCHANGE_API_URL=https://api.indexexchange.com ## API Reference -88 endpoints across 25 groups: +89 endpoints across 25 groups: | Group | Endpoints | Description | |-------|-----------|-------------| @@ -223,7 +223,7 @@ INDEX_EXCHANGE_API_URL=https://api.indexexchange.com | Packages | 7 | Tier-gated package CRUD, assembly, and sync | | Products | 6 | Product catalog, avails, inventory type overrides | | Quotes | 2 | Non-binding price quotes (IAB Deals API) | -| Deal Booking | 11 | Deals, from-template, push, distribute, migrate, deprecate, lineage, export | +| Deal Booking | 12 | Deals, list, from-template, push, distribute, migrate, deprecate, lineage, export | | Deals | 1 | Deal creation from accepted proposals | | Deal Performance | 1 | Delivery metrics | | Bulk Operations | 1 | Batch deal create/update/cancel | @@ -238,7 +238,7 @@ INDEX_EXCHANGE_API_URL=https://api.indexexchange.com | Supply Chain | 1 | sellers.json-like self-description | | Curators | 4 | Curator registration + curated deals | | Sessions | 5 | Multi-turn session persistence | -| Authentication | 4 | API key management | +| Authentication | 5 | API key management | | Agent Registry | 6 | Agent card, trust + discovery | | Pricing | 3 | Rate card + pricing calculation | | Events | 2 | Event log queries | diff --git a/docs/api/openapi.json b/docs/api/openapi.json index 4711c219..c9763d68 100644 --- a/docs/api/openapi.json +++ b/docs/api/openapi.json @@ -1937,6 +1937,86 @@ } } } + }, + "get": { + "tags": [ + "Deal Booking" + ], + "summary": "List Deals", + "description": "List stored deals, optionally filtered by wire status.\n\nOperator-only: the list spans every buyer's deals. Buyers read their\nown deal with ``GET /api/v1/deals/{deal_id}``.\n\n``status`` is the shared :class:`DealStatus` value the deal carries on\nthe wire (the same value ``GET /api/v1/deals/{deal_id}`` returns), so\nthe filter is applied after mapping each stored record to its\nresponse shape.\n\nRegistered ahead of ``/api/v1/deals/{deal_id}``; the two paths differ in\nlength so there is no shadowing, but keeping literal routes together\nwith ``/export`` keeps the EP-8.4 ordering rule easy to audit.", + "operationId": "list_deals_api_v1_deals_get", + "parameters": [ + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/DealStatus" + }, + { + "type": "null" + } + ], + "title": "Status" + } + }, + { + "name": "authorization", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Authorization" + } + }, + { + "name": "X-Api-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DealListResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } } }, "/agentic-audience/match": { @@ -7700,6 +7780,36 @@ "title": "DealFromTemplateResponse", "description": "Response for template-based deal creation." }, + "DealListResponse": { + "properties": { + "deals": { + "items": { + "$ref": "#/components/schemas/DealBookingResponse" + }, + "type": "array", + "title": "Deals" + }, + "count": { + "type": "integer", + "title": "Count" + }, + "skipped": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Skipped", + "default": [] + } + }, + "type": "object", + "required": [ + "deals", + "count" + ], + "title": "DealListResponse", + "description": "Page of stored deals in the shared booking-response shape." + }, "DealMigrationRequest": { "properties": { "old_deal_id": { diff --git a/docs/api/overview.md b/docs/api/overview.md index 76d8defd..5430ee7c 100644 --- a/docs/api/overview.md +++ b/docs/api/overview.md @@ -1,6 +1,6 @@ # API Overview -The Ad Seller System API exposes **88 endpoints** across **25 tags**. All endpoints are served from a single FastAPI application. The complete auto-generated route inventory is in the [REST Endpoints reference](../reference/endpoints.md). +The Ad Seller System API exposes **89 endpoints** across **25 tags**. All endpoints are served from a single FastAPI application. The complete auto-generated route inventory is in the [REST Endpoints reference](../reference/endpoints.md). **Base URL:** `http://localhost:8000` **OpenAPI docs:** `http://localhost:8000/docs` diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 86cbcc0b..6ac8ec24 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -20,7 +20,7 @@ graph LR subgraph "Seller Agent" MCP_S["/mcp/ (Streamable HTTP)
MCP Server"] A2A_S["/a2a/seller/jsonrpc
A2A Server"] - REST_S["REST API
88 endpoints"] + REST_S["REST API
89 endpoints"] NLP[NL Processing] TOOLS[Seller Tools] FLOWS[CrewAI Flows] @@ -61,7 +61,7 @@ graph TB subgraph "Protocol Layer" MCP[MCP Server
/mcp/ Streamable HTTP] A2A[A2A Server
/a2a/seller/jsonrpc] - API[REST API
88 endpoints, 25 tags] + API[REST API
89 endpoints, 25 tags] end AUTH[Auth & API Keys] @@ -135,7 +135,7 @@ graph TB ### API Layer -**FastAPI application** with 88 endpoints across 25 OpenAPI tags. Handles HTTP routing, request validation, authentication, and response serialization. See [API Overview](../api/overview.md). +**FastAPI application** with 89 endpoints across 25 OpenAPI tags. Handles HTTP routing, request validation, authentication, and response serialization. See [API Overview](../api/overview.md). ### Authentication and Agent Registry diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 51b0f469..4f5cecd0 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -62,7 +62,7 @@ Expected response: ## Browse the API Docs -Open `http://localhost:8000/docs` in a browser for the auto-generated Swagger UI with all 88 endpoints. +Open `http://localhost:8000/docs` in a browser for the auto-generated Swagger UI with all 89 endpoints. ## First API Calls @@ -118,6 +118,6 @@ Returns a confirmed deal with a Deal ID, OpenRTB parameters, and DSP activation ## Next Steps -- [API Overview](../api/overview.md) --- see all 88 endpoints +- [API Overview](../api/overview.md) --- see all 89 endpoints - [Authentication](../api/authentication.md) --- set up API keys for authenticated access - [Buyer Agent Integration](../integration/buyer-agent.md) --- connect a buyer agent diff --git a/docs/guides/chatgpt-setup.md b/docs/guides/chatgpt-setup.md index 4c000fba..a1df8eba 100644 --- a/docs/guides/chatgpt-setup.md +++ b/docs/guides/chatgpt-setup.md @@ -190,6 +190,6 @@ Base URL: https://your-publisher.example.com Auth: Authorization: Bearer sk-operator-XXXXX ``` -The seller agent exposes 88 REST endpoints. See the [API Overview](../api/overview.md). +The seller agent exposes 89 REST endpoints. See the [API Overview](../api/overview.md). For ChatGPT specifically, you can also create a **Custom GPT** with Actions pointing to the REST API's OpenAPI spec at `https://your-publisher.example.com/openapi.json`. diff --git a/docs/index.md b/docs/index.md index 19c77a73..42bc2dc5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,12 +12,12 @@ Part of the IAB Tech Lab Agent Ecosystem --- see also the [Buyer Agent](https:// |----------|----------|----------| | **[MCP](api/mcp.md)** | `/mcp/` (Streamable HTTP), `/mcp-sse/sse` (legacy) | Primary interface — 46 tools for Claude, ChatGPT, Codex, Cursor, and buyer agents | | **[A2A](api/a2a.md)** | *(planned — not yet served)* | Conversational agent interactions — natural language, multi-turn | -| **[REST API](api/overview.md)** | `/api/v1/*` | Programmatic access — 88 endpoints across 25 groups | +| **[REST API](api/overview.md)** | `/api/v1/*` | Programmatic access — 89 endpoints across 25 groups | ## Key Capabilities - **46 MCP tools** for Claude, ChatGPT, Codex, Cursor, and Windsurf — interactive setup wizard + full operations -- **88 REST endpoints** across 25 categories covering the complete ad selling workflow +- **89 REST endpoints** across 25 categories covering the complete ad selling workflow - **Pluggable ad server** support — Google Ad Manager and FreeWheel (Streaming Hub + Buyer Cloud) - **Multi-SSP distribution** — PubMatic (MCP), Index Exchange (REST), Magnite (REST) with routing rules - **IAB Deals API v1.0** — standardized deal push to buyer DSPs @@ -54,7 +54,7 @@ Part of the IAB Tech Lab Agent Ecosystem --- see also the [Buyer Agent](https:// ### API Reference -- [API Overview](api/overview.md) --- all 88 endpoints grouped by tag +- [API Overview](api/overview.md) --- all 89 endpoints grouped by tag - [MCP Protocol](api/mcp.md) --- 46 MCP tools for Claude, ChatGPT, and buyer agents - [A2A Protocol](api/a2a.md) --- conversational agent-to-agent interface - [Agent Discovery](api/agent-discovery.md) --- `/.well-known/agent.json` and trust registry From 1115d6fc5b846f4fed10fd13d6d01a4239909798 Mon Sep 17 00:00:00 2001 From: numaras Date: Mon, 5 Oct 2026 14:02:22 -0400 Subject: [PATCH 5/5] docs: regenerate openapi.json for the product route descriptions Main updated the list_products and get_product docstrings to describe the stored inventory-type override, so the committed document's two descriptions fell behind and the drift test failed after merging main. No operations changed; the total stays 89. Co-Authored-By: Claude Opus 5.5 --- docs/api/openapi.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/openapi.json b/docs/api/openapi.json index c9763d68..4730f858 100644 --- a/docs/api/openapi.json +++ b/docs/api/openapi.json @@ -1016,7 +1016,7 @@ "Products" ], "summary": "List Products", - "description": "List products in the catalog (shared ProductListResponse).\n\nReads from the cached static catalog (see `_get_static_product_catalog`)\ninstead of running ProductSetupFlow per request \u2014 kicking off the flow\nspins up an OpenDirect MCP session that hangs in `session.initialize()`.\nBuyers filter client-side over the returned Product records (there is\ndeliberately no POST /products/search on the shared catalog surface).", + "description": "List products in the catalog (shared ProductListResponse).\n\nReads from the cached static catalog (see `_get_static_product_catalog`)\ninstead of running ProductSetupFlow per request \u2014 kicking off the flow\nspins up an OpenDirect MCP session that hangs in `session.initialize()`.\nBuyers filter client-side over the returned Product records (there is\ndeliberately no POST /products/search on the shared catalog surface).\n\nAny stored inventory-type override (AI-14) is applied automatically \u2014\n``deps.get_product_catalog()`` itself now returns the catalog with\noverrides already applied, so every consumer sees one consistent view,\nnot just this route and ``get_product``.", "operationId": "list_products_products_get", "parameters": [ { @@ -1131,7 +1131,7 @@ "Products" ], "summary": "Get Product", - "description": "Get a specific product (shared Product primitive, no wrapper).\n\nReads from the cached static catalog instead of running ProductSetupFlow\nper request (see `list_products` for rationale).", + "description": "Get a specific product (shared Product primitive, no wrapper).\n\nReads from the cached static catalog instead of running ProductSetupFlow\nper request (see `list_products` for rationale). Any stored\ninventory-type override (AI-14) is applied automatically by\n``deps.get_product_catalog()``.", "operationId": "get_product_products__product_id__get", "parameters": [ {