Skip to content

SSP Connectors

SSP connectors import deals from supply-side platforms directly into the deal library. Each connector speaks its SSP's API format and normalizes deals to DealStore.save_deal() kwargs.

Source: src/ad_buyer/tools/deal_library/connectors/


Abstract Base Class

All connectors implement the SSPConnector abstract base class (tools/deal_library/ssp_connector_base.py).

class SSPConnector(ABC):

    @property
    @abstractmethod
    def ssp_name(self) -> str:
        """Human-readable SSP name (e.g. "PubMatic")."""

    @property
    @abstractmethod
    def import_source(self) -> str:
        """Tag written to portfolio_metadata (e.g. "PUBMATIC")."""

    @abstractmethod
    def fetch_deals(self, **kwargs) -> SSPFetchResult:
        """Fetch and normalize deals from the SSP API."""

    @abstractmethod
    def _normalize_deal(self, raw_deal: dict) -> dict:
        """Map one SSP API deal object to DealStore kwargs."""

    def get_required_config(self) -> list[str]:
        """Return required environment variable names."""
        return []

    def is_configured(self) -> bool:
        """True if all required env vars are set and non-empty."""

The SSPFetchResult dataclass is the standard return type:

Field Type Description
deals list[dict] Normalized deals ready for DealStore.save_deal()
errors list[str] Human-readable normalization error messages
total_fetched int Raw deal count from the API
successful int Deals successfully normalized
failed int Deals that failed normalization
skipped int Duplicates or filtered-out deals
ssp_name str SSP name for logging

Per-SSP Implementations

PubMatic

File: connectors/pubmatic.py

PubMatic exposes a buyer-facing REST PMP API. The connector fetches PMP, PG, and Preferred deals from the buyer's seat.

Detail Value
Base URL https://api.pubmatic.com
Auth Bearer token (Authorization: Bearer <token>)
Endpoint GET /pmp/deals
Pagination Page-based; iterates until an empty page
Required env vars PUBMATIC_API_TOKEN, PUBMATIC_SEAT_ID

Fetch filters (passed as kwargs):

Filter Values Default
status active, inactive, pending, all all
deal_type PG, PMP, preferred, all all
page_size 1–500 100

Deal type mapping: pgPG, preferredPD, pmpPA.


Magnite

File: connectors/magnite.py

Magnite operates two separate platforms with different API endpoints and deal inventory.

Platform Constant Base URL Focus
Streaming (CTV/OTT) PLATFORM_STREAMING https://api.tremorhub.com CTV — Roku, Fire TV, Samsung TV Plus
DV+ (display/video) PLATFORM_DV_PLUS https://api.rubiconproject.com Display and video

Auth is session-based on both platforms: POST /v1/resources/login with access key and secret key to receive a session cookie. All subsequent requests include that cookie.

Detail Value
Deals endpoint GET /v1/resources/seats/{seat_id}/deals
Required env vars MAGNITE_ACCESS_KEY, MAGNITE_SECRET_KEY, MAGNITE_SEAT_ID
Optional env var MAGNITE_PLATFORM (streaming or dv_plus; default: streaming)

Magnite does not provide a buyer-facing deal creation API. The connector reads deals that sellers have already targeted to the buyer's seat ID.


Index Exchange

File: connectors/index_exchange.py

Index Exchange uses API key authentication and a simple REST endpoint. Publishers create deals in IX and specify buyer seat IDs; this connector discovers deals that have been targeted to the buyer's seat.

Detail Value
Base URL https://api.indexexchange.com
Auth API key in X-API-Key header
Seat filtering seatId query parameter
Endpoints GET /deals, GET /deals/{deal_id}
Required env vars IX_API_KEY, IX_SEAT_ID

Deal type mapping: PGPG, PDPD, PMPPA.


Normalization to DealStore Schema

All connectors map their SSP-specific fields to the same DealStore.save_deal() kwargs. The minimum required fields for every normalized deal:

Field Source
seller_deal_id SSP deal ID (used in OpenRTB bid requests)
product_id Set to seller_deal_id if no separate product ID
display_name Deal name from SSP
seller_org SSP name (e.g., "PubMatic")
seller_type Always "SSP"
seller_url SSP API base URL
deal_type Normalized to PG, PD, PA, OPEN_AUCTION, UPFRONT, or SCATTER
status Set to "imported" for new deals

Optional fields (populated when available from the SSP API): fixed_price_cpm, bid_floor_cpm, currency, formats, geo_targets, content_categories, audience_segments, flight_start, flight_end, impressions, description, media_type, seller_domain.


Error Handling

Three exception types are defined in ssp_connector_base.py:

Exception Trigger Attributes
SSPAuthError HTTP 401 or 403 status_code
SSPRateLimitError HTTP 429 retry_after (seconds, from Retry-After header)
SSPConnectionError HTTP 5xx, network timeout, DNS failure status_code

fetch_deals() raises these exceptions rather than swallowing them. The caller (typically an MCP tool) is responsible for catching and surfacing errors to the AI assistant.

Individual deal normalization failures (KeyError, ValueError from _normalize_deal) are caught inside fetch_deals(), recorded in SSPFetchResult.errors, and counted in SSPFetchResult.failed. A normalization failure on one deal never blocks the rest of the batch.


Sync Flow

sequenceDiagram
    participant MCP as MCP Tool
    participant Conn as SSPConnector
    participant API as SSP API
    participant Store as DealStore

    MCP->>Conn: fetch_deals(status="active")
    Conn->>API: GET /deals (page 1)
    API-->>Conn: raw deal list
    Conn->>Conn: _normalize_deal() × N
    loop more pages
        Conn->>API: GET /deals (page N+1)
        API-->>Conn: raw deal list
    end
    Conn-->>MCP: SSPFetchResult
    loop each deal in result.deals
        MCP->>Store: save_deal(**deal)
        MCP->>Store: save_portfolio_metadata(import_source=SSP_TAG)
    end
    MCP-->>AI: summary (total, successful, failed, skipped)

Adding a New Connector

Extend SSPConnector and implement four members:

from ad_buyer.tools.deal_library.ssp_connector_base import (
    SSPConnector, SSPFetchResult, SSPAuthError, SSPConnectionError, SSPRateLimitError
)

class MySSPConnector(SSPConnector):

    @property
    def ssp_name(self) -> str:
        return "My SSP"

    @property
    def import_source(self) -> str:
        return "MY_SSP"

    def get_required_config(self) -> list[str]:
        return ["MY_SSP_API_KEY", "MY_SSP_SEAT_ID"]

    def fetch_deals(self, **kwargs) -> SSPFetchResult:
        result = SSPFetchResult(ssp_name=self.ssp_name)
        # Call the SSP API, iterate pages, call _normalize_deal()
        return result

    def _normalize_deal(self, raw_deal: dict) -> dict:
        return {
            "seller_deal_id": raw_deal["id"],
            "product_id": raw_deal["id"],
            "display_name": raw_deal["name"],
            "seller_org": self.ssp_name,
            "seller_type": "SSP",
            "seller_url": "https://api.myssp.com",
            "deal_type": ...,  # map to PG / PD / PA / etc.
            "status": "imported",
        }

Register the new connector in connectors/__init__.py and wire it into the MCP tools (sync_ssp_deals, test_ssp_connection) in interfaces/mcp_server.py.