{"api_version":"2026-10-27","total_operations":432,"surfaces":["data","demand","measurement","partner"],"operations":[{"surface":"data","method":"GET","path":"/v1/data/aggregate","operation_id":"dataApiAggregate","summary":"Aggregate audience analytics for a time window.","description":"Groups audience metrics by the requested dimension and returns summary statistics. Choose the dimension with `group_by` and the measure with `metric`; each lists its allowed values on the parameter.","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Data API"],"doc_url":"https://api.trillboards.com/docs#data","example_request":{"method":"GET","path":"/v1/data/aggregate"}},{"surface":"data","method":"GET","path":"/v1/data/lookalike","operation_id":"dataApiLookalikeScreens","summary":"Find screens with similar audience profiles.","description":"Returns screens ranked by audience similarity to the source screen. Use this for lookalike targeting expansion.","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Data API"],"doc_url":"https://api.trillboards.com/docs#data","example_request":{"method":"GET","path":"/v1/data/lookalike"}},{"surface":"data","method":"GET","path":"/v1/data/screens","operation_id":"dataApiListScreens","summary":"List screens visible to the caller.","description":"Paginated list of screens under the caller's API key scope. Returns the current status and venue metadata for each screen.","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Data API"],"doc_url":"https://api.trillboards.com/docs#data","example_request":{"method":"GET","path":"/v1/data/screens"}},{"surface":"data","method":"GET","path":"/v1/data/screens/{screenId}/audience","operation_id":"dataApiScreenAudienceLive","summary":"Real-time audience snapshot for a screen.","description":"Returns the latest live audience snapshot for the screen. When no snapshot is available the `audience` field is null.","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Data API"],"doc_url":"https://api.trillboards.com/docs#data","example_request":{"method":"GET","path":"/v1/data/screens/<screenId>/audience"}},{"surface":"data","method":"GET","path":"/v1/data/screens/{screenId}/history","operation_id":"dataApiScreenAudienceHistory","summary":"Historical audience_metrics rows for a screen.","description":"Time-range query over the `audience_metrics` table. Defaults to the 100 most recent windows.","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Data API"],"doc_url":"https://api.trillboards.com/docs#data","example_request":{"method":"GET","path":"/v1/data/screens/<screenId>/history"}},{"surface":"data","method":"GET","path":"/v1/data/semantic-search","operation_id":"dataApiSemanticSearch","summary":"Similarity search over scene embeddings.","description":"Vector similarity search against recently-ingested audience scene embeddings. Useful for finding screens or moments matching a natural-language prompt.","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Data API"],"doc_url":"https://api.trillboards.com/docs#data","example_request":{"method":"GET","path":"/v1/data/semantic-search"}},{"surface":"data","method":"GET","path":"/v1/data/stats","operation_id":"dataApiStats","summary":"Platform-wide counters.","description":"Returns active screens, embeddings stats, and recent metrics counts for the authenticated partner.","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Data API"],"doc_url":"https://api.trillboards.com/docs#data","example_request":{"method":"GET","path":"/v1/data/stats"}},{"surface":"data","method":"GET","path":"/v1/data/stream","operation_id":"dataApiStream","summary":"Server-Sent Events stream of live audience/observation events.","description":"Opens an SSE connection delivering live audience + observation events. Filter with `observation_family` or `venue_type` query params.","agent_safe":true,"idempotent":false,"cost_tier":"metered","llm_example":null,"tags":["Data API"],"doc_url":"https://api.trillboards.com/docs#data","example_request":{"method":"GET","path":"/v1/data/stream"}},{"surface":"data","method":"GET","path":"/v1/data/usage","operation_id":"dataApiUsage","summary":"API usage for the authenticated key (billing dashboard).","description":"Returns per-day request counts and billable records over the requested window. Used by the metering/billing dashboard.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Data API"],"doc_url":"https://api.trillboards.com/docs#data","example_request":{"method":"GET","path":"/v1/data/usage"}},{"surface":"data","method":"GET","path":"/v1/example/healthcheck","operation_id":"exampleHealthcheck","summary":"Example healthcheck (Phase 3 pipeline proof)","description":"Proof-of-concept operation wired through the Zod → OpenAPI 3.1 generator. Later migration phases replace this with real Data API operations.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":"Ping this endpoint before any real call to confirm the API is reachable and your network route is healthy.","tags":["Data API"],"doc_url":"https://api.trillboards.com/docs#data","example_request":{"method":"GET","path":"/v1/example/healthcheck"}},{"surface":"data","method":"POST","path":"/v1/data/query","operation_id":"dataApiQueryDsl","summary":"Composable query DSL over approved datasets.","description":"FEIN query DSL. Supply a `dataset`, optional `dimensions`, `measures`, `filters`, and `order_by`. Invalid dimensions or measures return `invalid_dimensions` / `invalid_measures` with the allowed set in `help`. `inference_outcomes` returns camera measurements (`inference_source = device`) unless the request filters on `inference_source`. Every `inference_outcomes` row carries `provenance`, and the response states which sources were read.","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Data API"],"doc_url":"https://api.trillboards.com/docs#data","example_request":{"method":"POST","path":"/v1/data/query"}},{"surface":"demand","method":"DELETE","path":"/openrtb/v1/events/cleanup","operation_id":"cleanupOldEvents","summary":"Delete old events","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"DELETE","path":"/openrtb/v1/events/cleanup"}},{"surface":"demand","method":"DELETE","path":"/openrtb/v2/fein/webhooks/{id}","operation_id":"deleteFeinWebhook","summary":"Delete FEIN webhook","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["FEIN Webhooks"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"DELETE","path":"/openrtb/v2/fein/webhooks/<id>"}},{"surface":"demand","method":"DELETE","path":"/openrtb/v2/webhooks/{id}","operation_id":"deleteWebhook","summary":"Remove a webhook subscription","description":"Deletes a webhook subscription. Ownership is verified against the\nauthenticated DSP seat — only the seat that created the webhook can delete\nit. Returns 404 if the webhook does not exist.\n\n**Rate limit category:** Campaigns (100 req/min).\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["DSP Webhooks"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"DELETE","path":"/openrtb/v2/webhooks/<id>"}},{"surface":"demand","method":"GET","path":"/openrtb","operation_id":"openrtbServiceRoot","summary":"OpenRTB service root — endpoint catalogue","description":"Returns a human-readable catalogue of the OpenRTB surface, grouped by\ncapability, with a one-line description per route.\n\nIt is a **convenience index for people**, not a machine contract: the\nkeys are `\"<METHOD> <path>\"` display strings with Express-style `:param`\nplaceholders, the groupings are hand-maintained, and nothing guarantees\nthe list stays in step with the routes actually mounted. Do not\ndiscover endpoints from it — this specification is the contract.\n\n`version` is the API major (`v1`), not a build number.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Internal"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb"}},{"surface":"demand","method":"GET","path":"/openrtb/bidder/{connectionAlias}/imp","operation_id":"partnerExchangeImpressionNotice","summary":"Record billable partner exchange impression","description":"Authenticated impression callback embedded inside creative markup `adm`\nfor VAST and site-banner resale. App-banner `openrtb_burl` bids return\nthis callback in the standard outer `burl`; supply must fire it only\nfrom the final SDK/display accounting event, never auction win or\ncreative prefetch. Both banner contracts first record a non-payable\nrender receipt and release the PostgreSQL win-then-billing outbox.\n`partner_bid_outcomes.served_at` advances only after durable downstream\nnotice delivery receives the required HTTP 2xx responses.\nThe outcome retains both supply and resale-demand lineage plus typed\nsupply dimensions for receivable reconciliation.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Partner Exchange Bidder"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/bidder/<connectionAlias>/imp"}},{"surface":"demand","method":"GET","path":"/openrtb/bidder/{connectionAlias}/loss","operation_id":"partnerExchangeLossNotice","summary":"Record partner exchange loss notice","description":null,"agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Partner Exchange Bidder"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/bidder/<connectionAlias>/loss"}},{"surface":"demand","method":"GET","path":"/openrtb/bidder/{connectionAlias}/spec","operation_id":"partnerExchangeSpec","summary":"Get partner exchange OpenRTB connection spec","description":"No-secret onboarding descriptor for an offline-registered partner exchange\nconnection. This is intentionally not a platform API-key surface; the alias\nreturns bid endpoints, accepted request objects/media types, self-contained\ncallback URL behavior, billable event semantics, user-sync support,\ncopy-pasteable OpenRTB request examples, and documentation links from\n`demand_partner_config`. Returned bids also retain the inbound inventory\nobject, domain, app bundle, and country on `partner_bid_outcomes`; those\nfields join the served settlement event to downstream demand-source\nreconciliation without creating a parallel impression ledger. When a\nconfigured resale pricing policy is dormant, the descriptor explicitly\nreports HTTP 204-only behavior, no bid objects, and no billable event.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Partner Exchange Bidder"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/bidder/<connectionAlias>/spec"}},{"surface":"demand","method":"GET","path":"/openrtb/bidder/{connectionAlias}/win","operation_id":"partnerExchangeWinNotice","summary":"Record partner exchange win notice","description":"Win callback generated in OpenRTB `nurl`. When the connection has no\nshared notice secret, the returned URL is self-authenticating; when a\nnotice secret is configured, the returned URL carries `sig`. A win is\nauction telemetry only and never creates a payable impression.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Partner Exchange Bidder"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/bidder/<connectionAlias>/win"}},{"surface":"demand","method":"GET","path":"/openrtb/health","operation_id":"openrtbHealth","summary":"OpenRTB service liveness probe","description":"Liveness only.\n\n**This probe checks no downstream dependency.** It does not touch\nPostgreSQL, ClickHouse or Redis; it reports that the Node process is\nanswering and returns its own heap and uptime. A `200` here therefore\nmeans the router is mounted and the event loop is turning, and says\nnothing about whether an ad can actually be served. Do not use it as a\nreadiness gate for the serving path.\n\n`status` at the top level is the literal `ok` on every response.\n`memory.status` is the only field that varies with health: `healthy`\nbelow 75% heap use, `warning` above 75%, `critical` above 90%. It is\ncomputed against `heapTotal`, which Node grows on demand, so a high\npercentage is not by itself evidence of a leak.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Internal"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/health"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/adpod/{token}","operation_id":"resolveAdPod","summary":"Resolve an ad-pod fulfilment token","description":"Ad pods (multiple back-to-back ad slots in one break) are fulfilled\nvia a signed token referencing a pre-built waterfall. Screens redeem\nthe token here to receive the full VAST document with multiple\n`<Ad>` elements in serving order. Single-use, expires after 60\nseconds.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Programmatic Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/adpod/<token>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/analytics/events","operation_id":"getEventAnalytics","summary":"Get event analytics","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/analytics/events"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/analytics/impressions","operation_id":"getImpressionAnalytics","summary":"Get impression analytics","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Impression Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/analytics/impressions"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/analytics/interactions","operation_id":"getInteractionAnalytics","summary":"Get interaction analytics","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Interaction Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/analytics/interactions"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/analytics/qr-scans","operation_id":"getQrScanAnalytics","summary":"Get QR scan analytics","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Interaction Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/analytics/qr-scans"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/bandwidth/analytics","operation_id":"getBandwidthAnalytics","summary":"Get aggregated bandwidth analytics","description":"Fleet-wide bandwidth savings over a date range, with daily, device-class,\nand connection-type breakdowns.\n\n## Numeric fields in the breakdowns are JSON strings\n\nOnly `summary` is coerced to numbers. `daily`, `by_device_class`, and\n`by_connection_type` are passed through from the database unmodified, so\nevery sum and count in them arrives as a **string** (`\"1000\"`, not\n`1000`) because the underlying columns are 64-bit integers. `date` in\n`daily` likewise serialises as a full ISO-8601 timestamp, not a bare\ncalendar date. Coerce before doing arithmetic, and do not type these as\nintegers in a generated client.\n\n`hit_rate_pct` here is a percentage to **two** decimal places — a third\nrounding convention alongside the fraction used by the VAST request\nanalytics and the one-decimal percentage used by the cache analytics.\n\nDates are passed straight into the query as `date` values, so an\nunparseable `start_date` produces a `500` rather than falling back.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Bandwidth Telemetry"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/bandwidth/analytics"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/bandwidth/screen-usage","operation_id":"getScreenBandwidthUsage","summary":"Get per-screen bandwidth usage","description":"Bandwidth and cache history for a single screen, broken out by day and\nsummarised per connection type. The per-screen counterpart to\n`GET /openrtb/v1/bandwidth/analytics`.\n\nBoth arrays are returned exactly as the database produced them, so —\nas on that endpoint — **the 64-bit and fixed-point numeric fields arrive\nas JSON strings**, and `date` is a full ISO-8601 timestamp. The two\nplain-integer counters in `daily` (`total_cache_hits`,\n`total_cache_misses`) are the exception and come back as numbers; in\n`summary_by_connection_type` even those are strings, because they are\nsummed. Coerce before doing arithmetic.\n\nEach `daily` row is one `(date, connection_type)` pair, so a screen that\nchanged connection during a day contributes more than one row for that\nday.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Bandwidth Telemetry"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/bandwidth/screen-usage"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/creative-blocklist","operation_id":"getCreativeBlocklist","summary":"Get creative blocklist","description":"Creative ids a player must refuse to render. Poll this and drop any\nmatching creative from a VAST response before it reaches the screen.\n\nCalled without `screen_id` it returns the **global** blocklist — every\ncreative reviewed and marked blocked platform-wide. Called with a\n`screen_id` it returns the global list unioned with the creatives that\nviolate that screen's own content preferences, deduplicated. Read\n`personalized` to know which of the two you received.\n\nThe global list is cached for five minutes, so a newly blocked creative\ncan take that long to appear.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["VAST Telemetry"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/creative-blocklist"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/creative-history/{screenId}","operation_id":"getCreativeHistory","summary":"Get creative history for a screen","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Review"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/creative-history/<screenId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/creative-intelligence/experiments","operation_id":"listCreativeExperiments","summary":"List creative experiments","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/creative-intelligence/experiments"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/creative-intelligence/experiments/{id}","operation_id":"getCreativeExperiment","summary":"Get creative experiment","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/creative-intelligence/experiments/<id>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/creative-intelligence/insights/{creativeId}","operation_id":"getCreativeInsights","summary":"Get creative insights","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/creative-intelligence/insights/<creativeId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/creative-intelligence/performance/{creativeId}","operation_id":"getCreativePerformance","summary":"Get creative performance metrics","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/creative-intelligence/performance/<creativeId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/creative-intelligence/top","operation_id":"getTopCreatives","summary":"Get top-performing creatives","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/creative-intelligence/top"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/creative-review","operation_id":"listCreativeReviews","summary":"List creative reviews","description":"Paginated list of creative safety reviews — the records behind\n`GET /openrtb/v1/creative-blocklist`. Each row carries the automated\ncontent classification, the review status, and the manual-review trail.\n\nRequires a bearer JWT whose `role` claim is `admin`. Note the unusual\nstatus mapping on the way in: a missing token is `401`, an **invalid**\ntoken is `403` (not `401`), and a valid non-admin token is also `403`\nwith a different body shape.\n\n## Pagination is unguarded\n\n`page` and `limit` are parsed but neither is clamped nor NaN-checked. A\nnon-numeric `page` produces a `500`. `limit` has no upper bound, so a\nlarge value will attempt to materialise that many rows. `limit=0`\nreturns no rows and reports `pagination.pages` as JSON `null`, because\nthe computed value is infinite and does not survive serialisation.\n\n`low_confidence` is a strict tri-state: exactly `true` or exactly\n`false` filter; **anything else, including `1`, is silently ignored** and\nyou get the unfiltered list.\n\nRows are ordered by `last_seen_at` descending. Note that individual\nrecords mix naming conventions — the id is `_id`, and timestamps are\n`createdAt`/`updatedAt` among otherwise snake_case fields.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Review"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/creative-review"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/creative-review/stats","operation_id":"getCreativeReviewStats","summary":"Get creative review statistics","description":"Whole-corpus counts for the creative review queue, plus the ten busiest\nad systems. Takes no parameters — it always reports on every review\nrecord, with no date window.\n\nRequires the same admin bearer JWT as `GET /openrtb/v1/creative-review`,\nand the same unusual status mapping applies (an invalid token is `403`,\nnot `401`).\n\n## The status buckets do not sum to `total`\n\n`total` counts every review record, but only five of the six review\nstates have a bucket: rows in the `metadata_only` state are inside\n`total` and inside no bucket. Compute a residual rather than assuming\n`pending + approved + flagged + blocked` accounts for everything.\n\nIn `top_ad_systems` the ad system name is keyed **`_id`**, not\n`ad_system`. The list is capped at ten entries and ordered by count\ndescending, so it is not a complete inventory of ad systems.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Review"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/creative-review/stats"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/event","operation_id":"trackEventGet","summary":"Track event via GET (pixel)","description":"Track an ad event via GET request. Returns a 1x1 transparent GIF pixel.\nUsed by VAST tracking URLs embedded in creatives. Includes GIVT (General\nInvalid Traffic) filtration with deduplication, screen liveness, and demo detection.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/event"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/ima-player/{screenId}","operation_id":"getImaPlayerHarness","summary":"HTML5 IMA SDK player harness for a screen","description":"Returns an HTML page that bootstraps Google's IMA SDK against\nTrillboards' VAST endpoint for the given screen. Used by partners\nwhose displays embed a browser-based player. The page is\nresponsive and adapts to the partner's screen aspect ratio.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Programmatic Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/ima-player/<screenId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/inline/{token}","operation_id":"resolveInlineAdToken","summary":"Resolve an inline-ad fulfilment token","description":"Inline ad delivery uses a short-lived signed token to avoid leaking\ncreative URLs to off-network actors. Screens redeem the token at this\nendpoint to fetch the inline VAST document; the token is single-use\nand expires after 60 seconds.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Programmatic Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/inline/<token>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/omsdk/event","operation_id":"recordOmsdkEventBeacon","summary":"Record OMSDK event (GET image-beacon variant)","description":"Beacon-friendly variant of `POST /openrtb/v1/omsdk/event` — the same\nhandler exposed as GET so a VAST player can fire OMSDK verification\nevents from an `<img>` / `navigator.sendBeacon` context where a request\nbody is not available. Event fields are passed as query parameters.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["OMSDK"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/omsdk/event"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/omsdk/report/{campaignId}","operation_id":"getOmsdkVerificationReport","summary":"Get OMSDK verification report","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["OMSDK"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/omsdk/report/<campaignId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/omsdk/session/{sessionId}","operation_id":"getOmsdkSessionStatus","summary":"DEPRECATED — returns 410 Gone","description":"This debug endpoint previously returned the full Mongo OMSDKSession document including\nthe `events[]` array. It had no production caller (verified via 14-day CloudWatch\nInsights scan, 2026-04-30) and was removed as part of the OMSDK Mongo→ClickHouse\ncutover (Phase 1b PR-2). Use ClickHouse-backed analytics endpoints for session/event\nlookups going forward.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["OMSDK"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/omsdk/session/<sessionId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/omsdk/verification.js","operation_id":"getOmsdkVerificationScript","summary":"Get OMSDK verification script","description":"Serves the JavaScript measurement agent a VAST player loads to drive an\nOMID session. Do not construct this URL by hand — take it from\n`session.verificationScriptUrl` in the create-session response, which\nalready carries the right `sid`.\n\nOnce loaded, the script fires `sessionStart`, `loaded`, and `impression`\nback to back, then polls geometry once a second, emitting\n`geometryChange`. It closes the session with `sessionFinish` on page\nunload, or after a five-minute inactivity timeout, whichever comes first.\nEvents are posted to `POST /openrtb/v1/omsdk/event` via `sendBeacon`,\nfalling back to `XMLHttpRequest` and then `fetch` — one event is sent\nbare, two or more are wrapped in `batch`.\n\nIt exposes `window.TrillboardsOMSDK` with `reportEvent`,\n`reportGeometry`, `finishSession`, and `getGeometry`, so a player can\ndrive measurement manually rather than relying on the automatic\nlifecycle.\n\n## This endpoint has no error status\n\n**Every response is `200`.** An unknown or expired `sid` is not an error:\nthe script is still served, with an empty session id baked in, and it\nthen silently declines to initialise — so measurement is off and nothing\non the wire says so. If session binding matters, verify it when you\ncreate the session, not here.\n\nA server-side failure is also `200`, returning a small stub that logs an\nerror to the browser console. Distinguish the two by the response\nheaders: the real script sends `Cache-Control: public, max-age=3600` and\nan `X-OMID-Partner` header, while the stub sends `Cache-Control:\nno-cache` and no `X-OMID-Partner`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["OMSDK"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/omsdk/verification.js"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/pixel","operation_id":"trackImpressionPixel","summary":"Track impression via pixel","description":"Impression beacon fired from an `<img>` tag or a VAST `<Impression>`\nnode. Shares one handler with `POST /openrtb/v1/impression`; the two\ndiffer only in how parameters arrive and in what a failure looks like on\nthe wire.\n\n## This beacon does not bill\n\nIt writes `impression_ledger` and an `impression_events` row with\n`status: \"started\"`, and increments delivery counters. It never writes\n`completed_impressions`. Revenue is settled by\n`POST /openrtb/v1/proof-of-play`; a fired pixel is evidence of a\n*started* play, not of a paid one.\n\n## The response is not always an image\n\nSuccess, a deduplicated repeat, a rejected signature, and an unhandled\nexception all return the same 42-byte transparent GIF with `200`. But a\nmissing-parameter or invalid-advertisement failure returns **JSON with a\n`400`**, and the rate limiter returns **JSON with a `429`** — so an\n`<img>` consumer sees a broken image with no way to read the reason.\nIntegrations that need to observe failures must use the POST form.\n\n## Signature is required\n\n`?sig=` is mandatory whenever the `VAST_ATTRIBUTION_REQUIRE_SIG` runtime\nflag is on, which is the production default. It is a truncated HMAC —\nsee the `sig` parameter for the exact construction, and note that it\ncovers a **fixed nine-field subset** of the query string. `impid`,\n`creative_id`, `campaign_id`, and `bid_price` are NOT signed, so\ntampering with them does not invalidate the signature.\n\n## Deduplication\n\nBest-effort, via a Redis `SET NX EX 3600` on\n`impression:dedup:{request_id or impid}:{screenId or \"none\"}`. Three\nconsequences worth designing around: the key uses `request_id` in\npreference to `impid`; `adid` is **not** part of the key; and when Redis\nis unreachable dedup is skipped entirely, while a Redis call slower than\n50 ms is treated as a duplicate and the impression is silently dropped.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Impression Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/pixel"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/vast","operation_id":"generateVast","summary":"Generate a VAST document for an ad","description":"Returns an inline VAST document for one advertisement.\n\n## Two different documents come out of this path\n\nWhich one you get is decided by whether you send `screenId`, and they\nare not the same VAST version:\n\n- **`screenId` supplied, and a servable owner placement exists** —\n  a **VAST 3.0** owner-direct document. Its tracking URLs are signed\n  and carry owner-billing parameters, and it has no\n  `ViewableImpression` and no `AdVerifications`.\n- **No `screenId`** — a **VAST 4.2** programmatic document with\n  `ViewableImpression` and an OMSDK `AdVerifications` extension.\n\nSo a client that hardcodes an expectation of 4.2 will break the moment\nit starts sending `screenId`. Parse the version rather than assuming\nit, and prefer following the wrapper as any compliant player does.\n\n## There is no empty-VAST no-fill on this path\n\nA request that cannot be filled is a `404` with a plain-text body, not\na `200` carrying an empty `<VAST/>`. That differs from\n`GET /openrtb/v1/vast/{partnerKey}/{screenId}`, which always answers\n`200` and uses an empty document to signal no-fill. A player wired to\nthis endpoint must treat a non-200 as no-fill.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Programmatic Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/vast"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/vast-cache/resolve/{creativeId}","operation_id":"resolveVastCacheEntry","summary":"Resolve a cached VAST creative by ID","description":"Returns the resolved cache entry for `creativeId` — the cached media\nURL on Trillboards' CDN, content-length, MIME type, first-seen and\nlast-seen timestamps, plus the upstream source URL. Screens hit this\nendpoint after parsing a `<MediaFile>` element to decide whether to\nstream from cache or upstream.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["VAST Telemetry"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/vast-cache/resolve/<creativeId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/vast-request/analytics","operation_id":"getVastRequestAnalytics","summary":"Get VAST request analytics","description":"Fill-rate and latency outcomes for VAST requests, aggregated by waterfall\nvariant over a bounded time window.\n\n## Rates here are fractions, not percentages\n\n`fill_rate`, `completion_rate`, and `effective_rate` are values in `0..1`\nrounded to four decimal places — a fill rate of 8% is `0.08`. The cache\nanalytics endpoints on this same surface report percentages instead\n(`hit_rate_pct`, `0..100`), so do not share a formatter between them.\n\n## The window is clamped\n\nThe lookback is capped at 14 days: a `start_date` older than that is\nsilently moved forward, an `end_date` in the future is pulled back to\nnow, and an `end_date` before the effective start is pushed up to it. An\nunparseable date falls back to the default rather than erroring. Read\n`period.start`, `period.end`, and `period.clamped` from the response to\nlearn the window you were actually served — never assume you got the\nwindow you asked for.\n\nServed from ClickHouse, falling back to an hourly PostgreSQL rollup. Both\nengines produce the same field set, so the source is not distinguishable\nfrom the response.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["VAST Telemetry"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/vast-request/analytics"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/vast-test/{screenId}","operation_id":"testVastForScreen","summary":"Generate a test VAST response for a screen","description":"Sandbox endpoint that returns a guaranteed-fill VAST response built\nfrom a small set of test creatives. Used during partner integration\nto validate that the screen's VAST parser handles the full set of\nIAB-spec-required elements. The returned creative is NOT a billable\nad.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Sandbox"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/vast-test/<screenId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/vast-wrap/{provider}/{screenId}","operation_id":"getVastWrapperByProvider","summary":"Generate a partner-wrapped VAST response","description":"Returns a VAST 4.2 document where the inner `<Wrapper>` element points\nat the demand-partner's VAST endpoint, while the outer document carries\nTrillboards' tracking macros (impression pixel, proof-of-play). Used\nwhen bidding partners require redirect-style ad serving.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Programmatic Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/vast-wrap/<provider>/<screenId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/vast/{partnerKey}/{screenId}","operation_id":"getVastByPartnerVariant","summary":"Generate VAST response on a partner-variant alias","description":"Path-parameterised entry-point used during partner onboarding to\nkeep partner-specific configuration (creative whitelist, eCPM\nfloor, deal IDs, custom macros) indexed by `partnerKey`. The\n`partnerKey` is issued at onboarding and MUST match the variant\nassigned to the caller's API key, or the request is rejected.\n\nThe returned document is the same VAST 4.2 shape produced by\n`GET /openrtb/v1/vast`; the only difference is that partner-specific\nconfiguration is selected by `partnerKey` rather than by API-key\nlookup, which lets a single partner test multiple configurations\nside-by-side from one credential.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Programmatic Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/vast/<partnerKey>/<screenId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/vast/{partnerKey}/{screenId}/debug","operation_id":"debugVastByPartnerVariant","summary":"Debug view of VAST waterfall for a partner-variant alias","description":"Returns the full waterfall decision log for the most recent VAST\nrequest on this `partnerKey` + `screenId` pair: per-bidder bids,\nno-bid reasons, cache lookups, blocklist hits, and timing\nbreakdowns. Intended for live debugging during partner onboarding.\nRate-limited per partner-variant.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Programmatic Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/vast/<partnerKey>/<screenId>/debug"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/vast/direct/{screenId}/{adid}","operation_id":"getFixedDirectVastTag","summary":"Serve a fixed first-party VAST tag for one booked ad and screen","description":"Stable URL used by managed-service publishers that configure a VAST tag\ndirectly in their own player. The URL itself remains fixed, while every\nrequest receives a fresh impression identity and signed lifecycle tracking\nbeacons. The endpoint fails closed unless the advertisement has a currently\nservable placement on the requested screen.\n\nThe response is a VAST 3.0 InLine document containing a progressive H.264\nMP4 MediaFile plus signed impression, quartile, completion, error, and\nproof-of-play reporting URLs. Responses are never cached.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Programmatic Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/vast/direct/<screenId>/<adid>"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/win","operation_id":"handleWinNotification","summary":"Handle win notification","description":"Win notification (`nurl`) fired by a DSP or exchange after it wins an\nauction. Trillboards puts this URL on every bid response as\n\n```\n{API_URL}/openrtb/v1/win?price=${AUCTION_PRICE}&adid=<ad-id>&impid=<imp-id>\n```\n\nThe `${AUCTION_PRICE}` macro is emitted **literally** — Trillboards never\nexpands it. Expanding it is the caller's job, and an unexpanded macro\narrives as a non-numeric `price` and is rejected.\n\n## This endpoint does not bill\n\nIt validates the price, resolves the advertisement, checks the campaign\nbudget, and runs spend-anomaly detection whose only sink is a log line.\nNo revenue row, no `total_spent` increment, and no win flag is written.\nBilling happens on `POST /openrtb/v1/proof-of-play`, the only path that\npublishes a completed impression and fires the `burl`. Treat a `200` here\nas \"the win was accepted for validation\", never as \"the impression was\npaid for\".\n\n## Response bodies are not JSON\n\nEvery branch except the `429` answers with `res.send(<string>)`, so the\nContent-Type is `text/html; charset=utf-8` and the body is a bare\nsentence. **Branch on the status code, never on the body.**\n\n## Price handling\n\n`parseFloat`, then a single guard: the value must be `> 0` and `<= 500`.\n`parseFloat` is prefix-tolerant, so `price=1.5USD` is accepted as `1.5`.\nThere is no currency parameter on this endpoint — the price is\ninterpreted in the currency of the bid response that carried the `nurl`.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Programmatic Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/win"}},{"surface":"demand","method":"GET","path":"/openrtb/v1/win-notice/{token}","operation_id":"relayPartnerWinNotice","summary":"Relay a demand partner's win notice at ad start","description":"The `start` tracking event a demand partner's win notice rides in. For a\npartner whose notice belongs at ad start (Playdigo), the VAST returned by\n`GET /openrtb/v1/vast/{partnerKey}/{screenId}` carries this URL as a\n`<Tracking event=\"start\">` instead of firing the partner's `nurl` when the\nbid arrives. The player calls it on the ad's first frame; the server\nanswers with the tracking pixel at once and then calls the stored `nurl`,\nonce per bid. A bid the player never starts never reaches the partner.\n\nThe `token` is a one-time key minted per bid. Unknown, expired, repeated\nand malformed tokens get the same pixel and call no one.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Impression Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v1/win-notice/<token>"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/account","operation_id":"getAccount","summary":"Get DSP account profile (commercial terms + deals)","description":"Returns the authenticated DSP's commercial profile — display/legal name,\nbilling email, payment terms, currency, tax id, and billing address — plus\nthe deals visible to the caller's seat.\n\nCommercial terms are sourced from the canonical billing-entity record\n(`dsp_billing_entities` + `dsp_ssp_contacts`), falling back to the DSP\nregistration's company name + billing email for newly-onboarded seats that\ndo not yet have a billing entity.\n\n**Scope:** requires `stats:read`. The `deals` array (with targeting such as\n`eligible_screens`) is included ONLY when the key also holds `deals:read`;\notherwise `deals` is empty and `deal_count` is 0.\n\nThe internal Stripe customer id is NEVER returned.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Reporting"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/account"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/adslots","operation_id":"getAdSlots","summary":"Browse available inventory with FEIN signals","description":"Returns OpenRTB 2.6 bid request objects for all active screens currently seeking\nprogrammatic demand. Each bid request includes live FEIN (Face/Edge Intelligence\nNetwork) signals in the `ext.trb.fein` extension when available.\n\n**This is the primary inventory discovery endpoint.** Use it to:\n- Browse all available screens with real-time audience data\n- Filter by geography, venue type, and audience signals\n- Get ready-to-use OpenRTB 2.6 bid request objects for your bidder\n\n**FEIN signal filtering:**\n- `min_vas=5.0` — Only screens with 5+ Verified Attention Seconds (7-day average)\n- `min_attention=60` — Only screens where 60%+ of detected faces are paying attention\n- `emotion=happy` — Only screens where the dominant audience emotion is \"happy\"\n\nFilters are combined with AND logic. Screens that lack FEIN sensors are excluded\nwhen FEIN filters are applied.\n\n**Signal coverage** is reported in the response. Not all screens have FEIN sensors:\n- `total_screens` — Total screens matching geographic/venue filters\n- `fein_enriched` — How many of those have live or historical audience data\n- `after_filters` — How many remain after applying FEIN signal filters\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Inventory"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/adslots"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/analytics/funnel","operation_id":"getVastFunnel","summary":"VAST event funnel (requests → renders → completions)","description":"Returns a VAST event funnel: `requests → bids_received → renders_started →\nplays_completed`, plus computed rates (`bid_rate`, `fill_rate`,\n`start_to_complete`), a top-50 per-screen breakdown, and optional timeseries\nat daily/hourly granularity.\n\n**Data source:** `openrtb_vast_screen_hourly` PG rollup table (OLTP-safe,\nrefreshed every 5 minutes from ClickHouse). In-memory cache: 5 minutes per\nquery fingerprint.\n\n**Funnel column mapping:**\n- `request_count` → `requests`\n- `response_count` → `bids_received`\n- `filled_count` → `renders_started` (ad began playing)\n- `completed_count` → `plays_completed` (ad played to full completion)\n\n**Rate limit category:** Reporting (100 req/min).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/analytics/funnel"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/audience/lookalike/{screenId}","operation_id":"findLookalikeScreens","summary":"Find lookalike screens","description":"Returns screens whose observed audience profile most resembles the\nsource screen's, for prospecting comparable inventory.\n\n## Authentication\n\nThis is a DSP surface, not a portal one. It takes the same\n**platform API key** as `GET /openrtb/v2/adslots` — as\n`Authorization: Bearer <platform_api_key>` or an `x-api-key` header —\nand the key must hold the `inventory:read` scope, which every onboarded\nDSP key carries. A portal user's JWT is not accepted here. The route\nshares the DSP inventory rate-limit budget.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/audience/lookalike/<screenId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/audience/predict/{screenId}","operation_id":"predictAudience","summary":"Predict audience for a screen and time window","description":"Projects the audience a screen is likely to have in a given hour-of-day\nand day-of-week slot, from its own history over `lookback_days`.\n\nA screen with too little history is **not** an error: the call returns\n`200` with `prediction: null` and a `message` saying so. Branch on\n`prediction`, never on the status code.\n\n## Authentication\n\nThis is a DSP surface, not a portal one. It takes the same\n**platform API key** as `GET /openrtb/v2/adslots` — as\n`Authorization: Bearer <platform_api_key>` or an `x-api-key` header —\nand the key must hold the `inventory:read` scope, which every onboarded\nDSP key carries. A portal user's JWT is not accepted here. The route\nshares the DSP inventory rate-limit budget.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/audience/predict/<screenId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/audience/semantic-search","operation_id":"semanticAudienceSearch","summary":"Semantic search across audience scene descriptions","description":"Free-text search over embedded scene descriptions, returning screens\nwhose recent observed context best matches the query.\n\n`min_similarity` filters on cosine distance, so raising it shrinks the\nresult set rather than reordering it. `since` bounds how far back the\nobservations may be drawn from.\n\n## Authentication\n\nThis is a DSP surface, not a portal one. It takes the same\n**platform API key** as `GET /openrtb/v2/adslots` — as\n`Authorization: Bearer <platform_api_key>` or an `x-api-key` header —\nand the key must hold the `inventory:read` scope, which every onboarded\nDSP key carries. A portal user's JWT is not accepted here. The route\nshares the DSP inventory rate-limit budget.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/audience/semantic-search"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/audience/semantic-stats","operation_id":"getAudienceSemanticStats","summary":"Get embedding statistics for semantic search","description":"Coverage of the scene-embedding corpus that backs\n`GET /openrtb/v2/audience/semantic-search`, over the **last 24 hours\nonly**. Use it to decide whether a semantic search is worth issuing\nbefore you issue one.\n\nTakes no parameters and no window can be requested.\n\n## Read the counters as strings\n\n`total_embeddings` and `unique_screens` are 64-bit database counters and\nare serialised as **JSON strings** (`\"14523\"`, not `14523`). Coerce\nbefore comparing or summing, and do not type them as integers in a\ngenerated client or deserialisation will fail.\n\nOn an empty 24-hour window the two counts are the string `\"0\"` while\n`earliest` and `latest` are `null`.\n\n## `stats` can be null\n\nWhen the backing store is unavailable the endpoint still returns `200`,\nwith `stats` set to `null` rather than an error. A `null` here means\n\"coverage unknown\", not \"no embeddings\" — do not report it as zero\ncoverage.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/audience/semantic-stats"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/creatives","operation_id":"listCreatives","summary":"List your creatives","description":"Returns all creatives for the authenticated DSP with moderation status, IAB\ncategories, and pagination. Use the `status` filter to find creatives in a\nspecific state (e.g., only approved creatives ready for campaigns).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creatives"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/creatives"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/creatives/{id}","operation_id":"getCreative","summary":"Get creative details with full moderation results","description":"Returns full creative details including AI moderation results. Use this endpoint\nto:\n\n- **Poll moderation status** after uploading a creative (check `moderation.status`)\n- **Review content classification** flags (alcohol, tobacco, gambling, etc.)\n- **Check age rating** (G, PG, PG-13, R, NC-17)\n- **View IAB categories** assigned by the AI pipeline\n- **See content analysis** (subjects, setting, mood, quality, keywords)\n- **Verify brand detection** results\n\n**Moderation timeline:**\n1. `status: pending_moderation` — Creative is in the queue (0-10 seconds)\n2. `status: pending_moderation` with `moderation.status: processing` — AI is analyzing (10-25 seconds)\n3. `status: approved` or `status: rejected` — Moderation complete (~30 seconds total)\n\n**Tip:** Poll every 5 seconds until `moderation.status` is `completed`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creatives"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/creatives/<id>"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/deals","operation_id":"listDeals","summary":"List active deals for your DSP","description":"Returns all active deals available to the authenticated DSP seat. Deals are\nfiltered by your seat ID — you only see deals where your DSP has been\nexplicitly invited or deals that are open to all registered DSPs.\n\n**Deal types:**\n- **PMP** — Private marketplace deal with floor price\n- **Preferred** — Preferred deal with fixed pricing and priority\n- **Guaranteed** — Guaranteed delivery with committed impression volume\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Deals"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/deals"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/deals/{dealId}","operation_id":"getDeal","summary":"Get deal details","description":"Returns full details for a specific deal, including impression delivery\nprogress, eligible venues and countries, and pricing terms.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Deals"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/deals/<dealId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/audience-segments","operation_id":"getFeinAudienceSegments","summary":"Get FEIN audience segments","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["FEIN Analytics"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/audience-segments"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/content-audience-correlation","operation_id":"getContentAudienceCorrelation","summary":"Content category x audience demographic breakdown","description":"Returns a breakdown of content categories crossed with venue types,\nshowing audience attention, face count, dwell time, and ad receptivity\nfor each combination. Useful for understanding which content types\ndrive the best audience engagement in which venue contexts.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Content Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/content-audience-correlation"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/content-performance/{videoId}","operation_id":"getContentPerformance","summary":"Get content performance by video ID","description":"Returns aggregated performance data for a single piece of content,\nincluding play counts, audience attention metrics, venue distribution,\ndaypart distribution, and top-performing screens.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Content Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/content-performance/<videoId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/content-recommendations","operation_id":"getContentRecommendations","summary":"Get best-performing content for a venue context","description":"Returns content recommendations ranked by a composite performance score\n(attention * 0.4 + replay density * 0.3 + dwell time * 0.3) for a given\nvenue type and optional daypart. Requires at least 3 plays to qualify.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Content Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/content-recommendations"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/creative-venue-performance","operation_id":"getCreativeVenuePerformance","summary":"Get creative x venue performance matrix","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["FEIN Analytics"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/creative-venue-performance"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/fill-rate-by-venue","operation_id":"getFillRateByVenue","summary":"Get fill rate by venue","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["FEIN Analytics"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/fill-rate-by-venue"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/inference-history","operation_id":"getFeinInferenceHistory","summary":"Get FEIN inference history","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["FEIN Analytics"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/inference-history"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/model-status","operation_id":"getFeinModelStatus","summary":"Get FEIN model status","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["FEIN Analytics"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/model-status"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/readiness","operation_id":"getFeinReadiness","summary":"Get FEIN readiness report","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["FEIN Analytics"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/readiness"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/screen-intelligence/{screenId}","operation_id":"getScreenIntelligence","summary":"Get screen intelligence","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["FEIN Analytics"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/screen-intelligence/<screenId>"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/stream","operation_id":"streamFeinEvents","summary":"Stream FEIN events over SSE","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["FEIN Webhooks"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/stream"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/fein/webhooks","operation_id":"listFeinWebhooks","summary":"List FEIN webhooks","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["FEIN Webhooks"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/fein/webhooks"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/market/cpm-benchmarks","operation_id":"getCpmBenchmarks","summary":"CPM percentile benchmarks by venue and geography","description":"Returns p25/median/p75/avg CPM percentiles per venue type, optionally\nfiltered by country, state, or daypart. Also returns a market overview with\ntotal SSP-enabled screens, average fill rate, and average completion rate.\n\n**Data source:** `bid_intelligence` and `openrtb_vast_screen_hourly` PG rollup\ntables. Cached in memory for 10 minutes per query.\n\n**Rate limit category:** Reporting (100 req/min).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Market Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/market/cpm-benchmarks"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/market/demand-analysis","operation_id":"getDemandAnalysis","summary":"Top categories, daypart curves, segment premiums","description":"Returns demand-side analytics: top advertiser categories by bid volume, daypart\ndemand curves (avg CPM + fill rate by morning/afternoon/evening/late_night),\nand segment CPM premium lift (if the segment correlation table is populated).\n\n**Data source:** `bid_intelligence` and `openrtb_vast_screen_hourly` PG rollup\ntables. Segment data is sourced from `segment_cpm_correlation` when available\n(graceful degradation returns an empty `segment_premiums` array otherwise).\n\n**Rate limit category:** Reporting (100 req/min).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Market Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/market/demand-analysis"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/openapi.json","operation_id":"getOpenRtbV2OpenApiSpec","summary":"Machine-readable OpenAPI 3 spec for the OpenRTB v2 surface","description":"Returns the OpenAPI 3 JSON document describing the `/openrtb/v2/*`\nendpoints (FEIN, audience reach, semantic search). Designed for\nagentic clients that build typed SDKs on demand. The v1 surface\nships its spec under `/docs/openapi/programmatic-api.yaml` (this\nfile).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Internal"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/openapi.json"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/proof-of-play","operation_id":"getProofOfPlay","summary":"Verified ad play records with tier breakdown","description":"Returns verified play records from `completed_impressions` so DSP buyers can\nreconcile billing against actual screen deliveries. Each record is tagged with\na `verification_tier`:\n\n- **`signed`** — Ed25519 signed proof-of-play from the screen (highest trust)\n- **`event_confirmed`** — VAST `complete` event fired by the IMA SDK\n- **`partner_reported`** — Partner SDK lifecycle completion report\n\nImpression callbacks are pixel fires, not completed plays, and are not\nreturned by this DSP-facing proof surface.\n\n**Required:** `start_date` and `end_date`. These are always included in the\nquery WHERE clause for partition pruning — the `completed_impressions` table\nis partitioned monthly, so omitting them would trigger a full scan and time out.\n\n**Rate limit category:** Reporting (100 req/min).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Proof of Play"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/proof-of-play"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/reports/creatives","operation_id":"getReportCreatives","summary":"Per-creative rollup for the authenticated DSP seat","description":"Returns a per-creative aggregate of delivery for the authenticated\nDSP seat over the requested lookback window. Sources directly from\nClickHouse (`mv_creative_screen_daily`); when CH is unavailable the\nendpoint returns `data: []` (200), not a 503 — same posture as the\nsibling earner content-performance endpoint. The legacy PostgreSQL\nmirror `openrtb_creative_screen_daily` was retired in CUT-11\n(2026-05). Results are sorted by `plays` descending.\n\nThe `source_provider` filter is applied implicitly: each DSP seat\nsees rows where `source_provider = inbound_dsp_<seat>` — the\ncanonical mapping established by the inbound bid cache and the\nwaterfall mediator.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Reporting"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/reports/creatives"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/reports/screens","operation_id":"getReportScreens","summary":"Per-screen performance breakdown","description":"Returns canonical billable completed impressions broken down by screen\nfor the authenticated DSP seat. Useful for identifying delivered screens\nand verifying campaign execution.\n\n**Data source:** ClickHouse `mv_completed_impressions_hourly`, implicitly\nfiltered to `source_provider = inbound_dsp_<seat>`. Results are sorted by\nimpressions descending. A ClickHouse outage returns `data: []` with `200`;\nno global PostgreSQL fallback is used.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Reporting"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/reports/screens"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/reports/summary","operation_id":"getReportSummary","summary":"Aggregated performance summary","description":"Returns billable completed-impression totals for the authenticated DSP seat.\nSeat isolation is applied implicitly with the canonical\n`source_provider = inbound_dsp_<seat>` mapping.\n\n**Data source:** the ClickHouse `mv_completed_impressions_hourly` rollup,\nderived from canonical `completed_impressions`. Maximum lookback: 90 days.\nIf ClickHouse is unavailable, the endpoint returns a zero-valued `200`\nrather than falling back to a global, non-seat-scoped PostgreSQL rollup.\n\n`filled`, `no_fill`, and `fill_rate` are compatibility fields on this\ncompletion-ledger report: every returned impression is filled/completed,\n`no_fill` is `0`, and `fill_rate` is `1` when delivery exists (otherwise\n`0`). Use `GET /stats` for seat-level bid and win metrics.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Reporting"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/reports/summary"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/reports/timeseries","operation_id":"getReportTimeseries","summary":"Time-series performance data","description":"Returns canonical billable completed-impression data points for the\nauthenticated DSP seat at hourly or daily granularity.\n\n**Data source:** ClickHouse `mv_completed_impressions_hourly`, implicitly\nfiltered to `source_provider = inbound_dsp_<seat>`. Each point includes\ncompleted impressions, revenue-weighted average CPM, and active screens.\nA ClickHouse outage returns `data: []` with `200`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Reporting"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/reports/timeseries"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/screens/{id}","operation_id":"getScreenDetail","summary":"Comprehensive screen profile","description":"Returns a full profile for a single screen — physical size, spot config,\nvenue intelligence, live + historical audience data, VAS scoring, 30-day\nperformance metrics, reliability, device capabilities, programmatic settings,\nand brand-safety flags.\n\n**Data sources (in order of latency):**\n1. **Redis** — live audience signals (~10 s refresh)\n2. **PostgreSQL** — screen metadata, venue enrichment, placements\n3. **ClickHouse** — performance rollups (30-day fill, completion, funnel)\n\n**Caching:** 2-minute in-memory cache keyed by `screen_id`. The `X-Cache`\nresponse header is `HIT` when served from cache, `MISS` otherwise.\n\n**Rate limit category:** Inventory (200 req/min production).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screens"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/screens/<id>"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/stats","operation_id":"getDSPStats","summary":"Get 24-hour performance stats","description":"Returns aggregated performance metrics for the authenticated DSP over the\nlast 24 hours.\n\n**Metrics returned:**\n- Total bids submitted\n- Total auction wins\n- Average CPM (clearing price)\n- Total revenue in USD\n- Average bid-to-win latency in milliseconds\n\n## A metric can be `null`, and `null` is not zero\n\nEvery metric is nullable. `null` means **not measured** — it is never a\nstand-in for zero, and the response never substitutes a placeholder value\nfor a missing one. Branch on the top-level `measured` boolean before you\nread a number, and read `stats.coverage` for the reason when it is\n`false`:\n\n| `stats.coverage` | Meaning |\n| --- | --- |\n| `measured` | Real figures. `matched_auctions` is the row count behind them. |\n| `seat_absent_from_auction_metadata` | No auction on our side is attributed to this seat in the window, so there is nothing to total. |\n| `clickhouse_unavailable` | We could not read the warehouse. This is our outage, not a statement about your bidding. |\n\nThis replaces earlier behaviour in which an empty or failed read returned\nfive zeros with a `200`, which read as a measurement (\"you bid zero\ntimes\") when it was in fact the absence of one.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Reporting"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/stats"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/webhooks","operation_id":"listWebhooks","summary":"List webhook subscriptions for this DSP seat","description":"Returns all webhooks for your DSP seat, ordered by creation time descending.\n**Secrets are never returned** by this endpoint — they are only shown once at\ncreation time.\n\nEach entry includes delivery stats (`success_count`, `failure_count`,\n`last_triggered_at`, `last_error`) so you can monitor webhook health.\n\n**Rate limit category:** Campaigns (100 req/min).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["DSP Webhooks"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/webhooks"}},{"surface":"demand","method":"GET","path":"/openrtb/v2/webhooks/deliveries","operation_id":"listWebhookDeliveries","summary":"Self-serve webhook delivery log","description":"Returns recent delivery attempts for this DSP seat's webhooks. Each delivery\nrecord captures the event name, target URL, HTTP response code, response\nbody sample, signature, and timestamp. Use this to debug failing deliveries\nwithout having to instrument your own receiver.\n\n**Note:** The delivery log table is being finalized in a follow-up release.\nUntil it ships, this endpoint returns an empty `data` array and a `note`\nfield indicating the stub state. Schema is stable — consumers can integrate\nnow and will begin receiving records when the backing table is live.\n\n**Rate limit category:** Campaigns (100 req/min).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["DSP Webhooks"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"GET","path":"/openrtb/v2/webhooks/deliveries"}},{"surface":"demand","method":"OPTIONS","path":"/openrtb/v1/vast/{partnerKey}/{screenId}","operation_id":"preflightVastByPartnerVariant","summary":"CORS preflight for the partner-variant VAST endpoint","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Programmatic Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"OPTIONS","path":"/openrtb/v1/vast/<partnerKey>/<screenId>"}},{"surface":"demand","method":"PATCH","path":"/openrtb/v1/creative-intelligence/experiments/{id}","operation_id":"updateCreativeExperiment","summary":"Pause, resume, or conclude creative experiment","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"PATCH","path":"/openrtb/v1/creative-intelligence/experiments/<id>"}},{"surface":"demand","method":"POST","path":"/openrtb/bidder/{connectionAlias}/bid","operation_id":"partnerExchangeBid","summary":"Bid on partner exchange inventory","description":"OpenRTB bidstream endpoint for offline-registered partner exchange connections.\nThis is not a platform API-key surface. The opaque `connectionAlias` resolves\n`demand_partner_config.adapter_params.partner_exchange_bidder`, applies connection admission,\nand answers through existing demand rails. SQL-controlled `initial_bid_mode=resale_waterfall`\nmaps inbound partner video/tag supply into `waterfallMediator.buildWaterfallOnly`; optional\n`resale_waterfall_partner_keys` reuses the existing per-screen waterfall override shape.\nWaterfall resale does not bid on a tag URL speculatively: explicitly allowed\npartners are tried sequentially in the explicit configured order within one\nshared inbound `tmax` budget. The first trusted VAST chain that returns a\nplayable, profitable inline creative is selected. The exact terminal\ncreative is returned in `adm` after inheriting impression, error, playback,\nand click trackers from every proven wrapper, with one render-time settlement\nbeacon. Empty/unplayable responses and timeouts return no bid and create no\npayable row.\nExplicit `resale_openrtb_partner_keys` reuses existing DPC OpenRTB demand rows,\n`OpenRTBAdapter`/`PrebidServerAdapter`, `BidCollector`, and the current OpenRTB\ncallback stores for `adm` passthrough across video, banner, audio, and native\nimpressions. Only the canonical `prebid_server` row selects PBS; PBS then uses\nits existing bidder config or the row's `adapter_params.custom_bidders`.\nThe endpoint does not auto-discover every DPC OpenRTB endpoint.\nA configured `resale_pricing_policy` is authoritative and fail-closed.\nCommercial resale requires a real positive downstream bid plus injectable\nVAST or banner HTML, explicit buyer/media/inventory identity scopes, and SQL-selected\ndownstream-minus or downstream-share pricing. The supply price must clear\nthe inbound floor and remain strictly below the rounded buyer bid. Auction\nwin is non-billable; every commercial bid returns `nurl` for auction\ntelemetry. Web banner `adm` contains the Trillboards render tracker and\nomits outer `burl`. App banner uses the distinct `openrtb_burl`\nsettlement contract and returns a signed outer `burl` that supply must\nfire only from its final-display accounting event. Video `adm` contains\nthe sole Trillboards VAST Impression and omits outer `burl`.\n`settlement_event=standard_render` lets one connection derive those\nconcrete contracts from each accepted inventory/media pair while\npreserving the same exact buyer, identity, seller-chain, and margin\ngates.\nA separately configured `resale_observation_policy` may call explicit\n`observation_openrtb_partner_keys` under stable sampling and fail-closed\nQPS limits to measure real buyer bid prices. The configured buyer keys\nmust exactly match the policy allowlist after case and duplicate\nnormalization; a mismatch is invalid and performs no buyer fanout.\nObservation always returns HTTP 204, creates no `partner_bid_outcomes`\nrow, stores no buyer notice URL, and fires no `nurl`, `burl`, or `lurl`.\nConfiguring observation and revenue policy active at the same time fails\nclosed.\nThe current bidder evaluates `imp[0]`; send one impression per request.\nAd Ops may temporarily enable bounded sanitized floor diagnostics on this\nsame DPC connection. Those samples retain top-level/PMP/quantity fields and\nexclude device IP/IFA; they do not change bidstream authentication or responses.\n`initial_bid_mode=internal_vast` remains available for booked `partner_demand_line_items`\nthat should render ordinary Trillboards inline VAST from `advertisement_mongo_id`.\nSuccessful bids include inline `adm` plus notice URLs. Waterfall-resale\nvideo omits `burl` because the Trillboards VAST wrapper contains the sole\nbillable `imp` beacon. Safe-policy OpenRTB VAST and web-banner responses\nalso omit outer `burl` because their markup contains the settlement\ntracker. App-banner `openrtb_burl` responses keep outer `burl` and do not\ninject a financial markup-load pixel.\nLegacy OpenRTB `adm` passthrough returns `burl` when upstream markup has no\nTrillboards settlement beacon; it must fire after render. VAST and legacy\nrender-time callbacks may advance `partner_bid_outcomes.served_at`\ndirectly. A safe-policy banner callback records `rendered_at`, releases\nordered buyer notices, and advances `served_at` only after billing HTTP\n2xx is durably recorded.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Partner Exchange Bidder"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/bidder/<connectionAlias>/bid"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/bandwidth-metric","operation_id":"recordBandwidthMetric","summary":"Track bandwidth telemetry","description":"Reports a screen's daily bandwidth consumption and cache effectiveness,\nsplit between ad video and content video.\n\n## Counters accumulate, they are not set\n\nThe row is keyed on `(screen_id, date, connection_type)` where `date` is\nalways the **server's** current date — it is not an input. On a repeat\ncall for the same key, all eight counters are **added** to what is\nalready stored; the descriptive fields are filled in only where they were\npreviously null. So send deltas since your last call, never running\ntotals, or the day's figures will compound. A retry after an ambiguous\nfailure will double-count.\n\nNote that `connection_type` is part of the key, so a screen that switches\nfrom wifi to cellular mid-day accumulates into two separate rows.\n\nOnly `application/json` is parsed; a `sendBeacon` defaulting to\n`text/plain` arrives with an empty body and is rejected as a missing\n`screen_id`.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Bandwidth Telemetry"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/bandwidth-metric"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/bid","operation_id":"processBidRequest","summary":"Process OpenRTB bid request","description":"Accepts an OpenRTB 2.6 bid request and returns matching bid responses.\nSupports DOOH (via `dooh` object) and web (via `site`/`app` objects).\nMaximum 10 impressions per request. Returns 204 No Content when no matching ads are available.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Programmatic Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/bid"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/command-ack","operation_id":"acknowledgeRemoteCommand","summary":"Acknowledge a remote command issued to a screen","description":"Screens POST here to acknowledge receipt and execution status of a\nremote command pushed to them (refresh, sleep, restart, profile\nupdate). Used by the fleet-management subsystem to mark commands\ncomplete in the audit log.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Fleet Management"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/command-ack"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/complete","operation_id":"trackCompletion","summary":"Track completion event","description":"Ad-completion beacon for custom players — integrations that render VAST\nthemselves instead of using the IMA SDK. It is the completion counterpart\nto the impression beacon on the same auction.\n\n## This endpoint settles revenue\n\nOn a qualifying call it publishes a `completed_impressions` row with\ningestion path `vast_event`. Four independent gates must all pass:\n\n- the invalid-traffic check passed (`givt.passed`);\n- the creative is not self-promotional inventory;\n- the demand provider resolves to something other than `unknown`;\n- an idempotency claim on\n  `(screenId, requestId, adId, impressionId, provider)` was won by this\n  call — a second call for the same tuple writes nothing.\n\nThe publish is fire-and-forget: the `200` is returned regardless, and a\nfailure in the billing pipeline is logged, not surfaced. **A `200` here\ndoes not confirm that a billable row landed.**\n\nIt differs from `POST /openrtb/v1/proof-of-play` in two ways that matter:\nthis endpoint carries no price, no play id and no signature, and unlike\nproof-of-play it **is** idempotent, so a retry is safe.\n\n## Rate-limit key trap\n\nThe limiter buckets on `device_id` (snake_case) read from the body,\nfalling back to client IP. The handler itself reads `deviceId`\n(camelCase). A caller that sends only `deviceId` is therefore rate-limited\n**by IP**, which on a shared NAT is a single 300/min bucket for the whole\nfleet. Send `device_id` as well if you want per-device budgeting.\n\n## Two different 200 bodies\n\nThe deduplicated response carries `givt.flags` and **no** `impressionId`;\nthe recorded response carries `impressionId` and no `givt`. Do not assume\neither field is present.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/complete"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/creative-intelligence/experiments","operation_id":"createCreativeExperiment","summary":"Create creative experiment","description":"Starts an A/B test across two or more creatives. The experiment begins\n`running` immediately — there is no separate start call.\n\nThe response is **`201`**, and its `experiment` is a three-field\nacknowledgement, not the full record. Read the experiment back with\n`GET /openrtb/v1/creative-intelligence/experiments/{id}` if you need\nthe variants, allocation, or metric.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Creative Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/creative-intelligence/experiments"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/creative-intelligence/experiments/{id}/analyze","operation_id":"analyzeCreativeExperiment","summary":"Run statistical analysis for experiment","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/creative-intelligence/experiments/<id>/analyze"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/creative-review/{creativeId}/block","operation_id":"blockCreative","summary":"Block creative","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Review"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/creative-review/<creativeId>/block"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/creative-review/{creativeId}/confirm","operation_id":"confirmCreativeReview","summary":"Confirm or override AI classification","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Review"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/creative-review/<creativeId>/confirm"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/creative-review/{creativeId}/unblock","operation_id":"unblockCreative","summary":"Unblock creative","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Review"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/creative-review/<creativeId>/unblock"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/event","operation_id":"trackEventPost","summary":"Track event via POST (JSON)","description":"Track an ad event via POST request. Returns a JSON acknowledgment.\nIncludes GIVT filtration. Deduplicated events return success with givt flags.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/event"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/heartbeat","operation_id":"trackHeartbeat","summary":"Track registered-screen heartbeat and targeting identities","description":"Records liveness, device telemetry, the actual resettable platform\nadvertising ID, and typed host-owned audience identities for a\nregistered device/screen.\n\nThis POST is intentionally unauthenticated. Supply either the stable\nregistered device fingerprint or its bound `screenId`; the resolved\nregistered-device relationship is the trust and attribution boundary.\nDo not send a Partner API key or device JWT.\n\n`advertising_identity.id` is the real GAID/AAID (or another supported\nplatform ID), never a screen UUID. `hem_sha256` is an existing\none-pass SHA-256 email digest and is not hashed again. `uid2_token` is\nopaque and case-sensitive; send it exactly as issued with its expiry.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/heartbeat"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/impression","operation_id":"recordImpression","summary":"Record impression","description":"JSON form of the impression beacon. Same handler as\n`GET /openrtb/v1/pixel`, so the ledger writes, the dedup rule, the\nsignature requirement, and the rate limit are all identical — see that\noperation for the full mechanics.\n\nPrefer this form over the pixel whenever the caller can read a response\nbody: every outcome here is machine-readable JSON, whereas the pixel\nreturns an opaque GIF for success, deduplication, signature rejection,\nand server error alike.\n\n**This beacon does not bill.** It records a *started* play. Revenue is\nsettled by `POST /openrtb/v1/proof-of-play`.\n\nTwo shape traps carried over from the shared handler:\n\n- The signature travels in the **query string** (`?sig=`) even on this\n  POST, and it covers query fields only — **the JSON body is entirely\n  unsigned**.\n- The body spells the ad id `adId` (camelCase), but the signature\n  canonicalisation uses `adid` (lowercase). They are different names for\n  the same value in different places.\n\n`mediaType` is accepted by the parser and then discarded; do not rely on\nit.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Impression Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/impression"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/impressions/batch","operation_id":"batchUpdateImpressions","summary":"Batch update impressions","description":null,"agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Impression Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/impressions/batch"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/interaction","operation_id":"trackInteraction","summary":"Track ad interaction","description":"Records a single viewer interaction with a playing ad — a touch, a click,\na QR scan. Analytics only; it never affects delivery counts or revenue.\n\nThe durable write is fire-and-forget and the `interactionId` in the\nresponse is minted before it settles, so a `200` with an id is not\nconfirmation that the row persisted.\n\n`interactionType` is **not** validated against an enum — any string is\naccepted and stored verbatim. The values the reporting side actually\nqueries are `qr_scan` and `qr_interaction` for the QR report; `click` and\n`touch` are the other conventional values. Inventing a new value will\nstore cleanly and then be invisible to every existing report.\n\n`metadata.userAgent` and `metadata.ip` are **overwritten** server-side\nfrom the request headers and socket. Anything you send under those two\nkeys is discarded.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Interaction Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/interaction"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/interaction-summary","operation_id":"trackInteractionSummary","summary":"Submit interaction summary","description":"Submits a whole interaction session in one call — the batch counterpart\nto `POST /openrtb/v1/interaction`. Analytics only; no billing effect.\n\n## The array items use a different field name\n\nEach element of `interactions` names its kind `type`, **not**\n`interactionType` as the single-interaction endpoint does. An item\nsending `interactionType` stores a null kind and is invisible to every\nreport. This is the most common mistake on this endpoint.\n\nTwo more asymmetries against the single-interaction endpoint: items get\n**no** `timestamp` default, so an item without one stores a null\ntimestamp; and `userAgent`/`ip` are **not** injected here.\n\nThe top-level `metadata` is merged into every item's own metadata and\n**wins on key collisions**, so a session-wide `campaignId` cannot be\noverridden per item.\n\n## The response is an echo, not a receipt\n\n`summary` restates what you sent plus a server timestamp. It contains no\nids and no write confirmation. The batch insert is awaited but its errors\nare swallowed, so a `200` can accompany a failed write. `interactionCount`\nis echoed verbatim and is **never** cross-checked against\n`interactions.length`.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Interaction Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/interaction-summary"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/omsdk/event","operation_id":"recordOmsdkEvent","summary":"Record OMSDK event","description":"Records one OMID measurement event, or a batch of them, against a session\nopened by `POST /openrtb/v1/omsdk/session`.\n\n## Single and batch forms behave differently on failure\n\nSend one event as a bare object. Send several by wrapping them in\n`batch`, up to 50 per request.\n\nIn the **single** form, a bad session or a bad event type is an HTTP\nerror: `404` when the session does not exist, `409` when it is already\ncompleted, `400` for an unknown event type or malformed `data`.\n\nIn the **batch** form **none of those statuses can occur**. Per-item\nfailures are collected into an `errors` array under a `200`, so a batch\nwhere every item failed still answers `200` with `processed: 0`. Always\nread `failed` and `errors`; do not rely on the status code. Only two\nthings fail a batch as a whole: exceeding 50 items (`400`) and the\nper-IP rate limit (`429`). The batch path also skips the per-session\nrate limit and does not attach device or user-agent context.\n\nThe `errors` key is **omitted entirely** when nothing failed, rather than\nbeing an empty array.\n\n## Two events close the session\n\n`complete` and `sessionFinish` mark the session completed. Every event\nsent afterwards is rejected with `409`. Order your quartiles accordingly,\nand send `sessionFinish` last.\n\n## Rate limits\n\n120 events per minute per session and 600 per minute per client IP, both\nsliding windows. These are separate from the OpenRTB tracking limiter and\nreturn their own body shape, shown under `429`.\n\nA GET variant of this same operation exists for beacon contexts where no\nrequest body is available.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["OMSDK"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/omsdk/event"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/omsdk/session","operation_id":"createOmsdkSession","summary":"Create OMSDK session","description":"Opens an Open Measurement (OMID 1.4) verification session for one ad\nplay. Everything else in the OMSDK flow hangs off the `sessionId` this\nreturns.\n\nThe response is **`201`**, not `200`. It also tells you where to go next:\n`verificationScriptUrl` is the script to load into the player, and\n`eventEndpoint` is where to post events — prefer both over hard-coding\npaths, since the session id is already embedded in the script URL.\n\nSessions live for **one hour** in the hot store. After that, or once a\n`complete` or `sessionFinish` event closes the session, further events\nfor that id are rejected — `404` if the session has aged out entirely,\n`409` if it is present but already completed.\n\n`adSessionType` is **not validated**: any string is accepted and stored.\nIt affects only the viewability thresholds, and the branch is a single\nequality test against `display`, so every other value — including a typo\n— is treated as video (50% of pixels for 2000 ms, against display's 100%\nfor 1000 ms).\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["OMSDK"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/omsdk/session"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/owned-direct/play-token/{screenId}","operation_id":"mintOwnedDirectPlayToken","summary":"Mint signed lifecycle URLs for one Schedule direct play","description":"Mints a fresh, replay-safe lifecycle map for one currently servable\nSchedule-mode direct placement. The device token and path both bind the\ncaller to `screenId`; the server revalidates the `(adId, screenId)`\nplacement and rejects self-promo before signing anything.\n\nEvery returned URL contains the same server-issued, HMAC-bound\n`request_id`. Fire the returned lifecycle URLs verbatim. A client\nimpression ID may be supplied for diagnostics, but it is not the\nbilling or replay identity. Responses are private and non-cacheable.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Event Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/owned-direct/play-token/<screenId>"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/performance","operation_id":"trackPerformance","summary":"Track performance telemetry","description":"Player and device health telemetry for a screen. Diagnostic only — it\nnever touches delivery counts, impressions, or revenue.\n\nBoth writes are fire-and-forget, so a `200` means the payload was\naccepted and dispatched, not that it was durably stored.\n\nOnly the seven metric keys enumerated below are read; any other key\ninside `metrics` is discarded silently. Values are copied through with no\nvalidation or unit coercion, so send them in the units documented here\nor the series will be uncomparable across fleets.\n\nUnlike `POST /openrtb/v1/complete`, `screenId` is only checked for\ntruthiness — there is no 24-hex validation and no rate limit on this\nendpoint.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Event Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/performance"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/proof-of-play","operation_id":"recordProofOfPlay","summary":"Record MRC DOOH proof-of-play","description":"MRC-aligned DOOH proof-of-play, fired by the screen application when an\nad finishes or is cut short.\n\n## This is the billing endpoint\n\nOf every beacon on this surface, this is the one that settles revenue.\nOn a qualifying call it publishes a `completed_impressions` row and\nfires the advertiser's billing URL (`burl`). Both are gated on ALL of:\n\n- the play completed — `completionType` is `full` or `complete`;\n- the invalid-traffic check passed (`givt.passed`);\n- the creative is not self-promotional inventory;\n- the demand provider resolves to something other than `unknown`\n  (an unresolved provider is dropped silently, still returning `200`).\n\n`POST /openrtb/v1/impression` and `GET /openrtb/v1/pixel` record a\n*started* play and never bill. `GET /openrtb/v1/win` validates a win and\nnever bills.\n\n## There is no deduplication\n\nUnlike the impression beacon, this endpoint has no dedup key. Every call\nmints a fresh `playId` and can publish a fresh billable row. **A blind\nretry double-bills.** Retry only on a transport failure where you have\nno response at all, and prefer making the play itself idempotent\nupstream.\n\n## The returned signature is minted by us, not verified from you\n\n`signature` in the response is a receipt Trillboards generates so a\nbuyer can verify the play independently. Nothing in the request carries\nan Ed25519 signature, and none is checked. The only credential you send\nis the HMAC `?sig=` query parameter.\n\nThe signed payload is six dot-joined fields in this exact order:\n\n```\n<signatureVersion>.<timestampMs>.<adId>.<impressionId>.<screenId>.<deviceId>\n```\n\nwhere `timestampMs` is `playStartTime` in epoch **milliseconds**, and\n`impressionId` falls back to the generated `playId` when you omit it.\n`completionType`, the durations, `givt`, `channel`, and `displayContext`\nare NOT covered. Verifiers must accept both prefixes: `ed25519=` (v2,\nthe normal path) and `sha256=` (v1, the fallback when no Ed25519 signing\nkey is configured).\n\n## Server-authoritative fields\n\n`impressionMultiplier` is resolved server-side from the audience-quantity\naudit for the auction and fails closed to `1`. A client-supplied value is\nread from the body and then discarded — sending it changes nothing.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Impression Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/proof-of-play"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/vast-cache-metric","operation_id":"recordVastCacheMetric","summary":"Track VAST cache metric","description":"Reports one VAST video cache hit or miss from a screen, so bandwidth\nsavings can be attributed.\n\n`cache_hit` is the only required field and is **strictly typed** — it\nmust be a JSON boolean. The string `\"true\"` is rejected with a `400`.\nEvery other field is optional and, when absent, defaults as documented\nbelow rather than erroring.\n\n## A 200 does not mean the row landed\n\nBoth the PostgreSQL insert and the ClickHouse mirror are best-effort:\neach is wrapped in its own error trap that logs and continues, and the\ncreative-media side effect is not awaited at all. The `200` confirms the\npayload was accepted and validated, nothing more. There is no storage\nfailure path that produces a `5xx`.\n\n## Content-Type must be JSON\n\nOnly `application/json` is parsed. A `navigator.sendBeacon` call that\ndefaults to `text/plain` arrives with an empty body and is rejected as a\nmissing `cache_hit`. Construct the `Blob` with an explicit\n`{ type: 'application/json' }`.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Bandwidth Telemetry"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/vast-cache-metric"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/vast-request","operation_id":"recordVastRequest","summary":"Track VAST request outcome","description":"Track pending, filled, no-fill, error, timeout, and completion states.","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["VAST Telemetry"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/vast-request"}},{"surface":"demand","method":"POST","path":"/openrtb/v1/viewability","operation_id":"trackViewability","summary":"Record viewability telemetry","description":"Records viewable-time telemetry against an impression already recorded by\nthe impression beacon. Not billable — it only updates viewability columns\non the existing impression row.\n\nThis is the only beacon on this surface with **no attribution signature\nand no rate limit**; it is completely open.\n\nAll input combinations return the documented `200`, including\n`viewable: true` with `viewableTime >= 1000`.\n\n> **Fixed.** Until this release, `viewable: true` **and**\n> `viewableTime >= 1000` answered `500 {\"error\":\"Internal server error\"}`:\n> the route wires the handler as a bare static reference, so `this` was\n> `undefined` and a sibling call threw. Because the PostgreSQL write is\n> awaited *before* that branch, the telemetry was stored and the caller\n> was told it had failed. Retrying was harmless — the write is an\n> idempotent `UPDATE` keyed on `impressionId`, never an insert, so a\n> retry rewrote the same row with the same values and could not duplicate\n> anything. Nothing needs to be resent or reconciled.\n\n`screenSize`, `adSize`, and `intersection` are parsed off the body and\nthen discarded — sending them has no effect.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Impression Tracking"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v1/viewability"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/audience/rank-creatives","operation_id":"rankCreativesForAudience","summary":"Rank creatives for an audience context","description":"Ranks a candidate set of creatives by how closely each one's historical\nplay context resembles the audience you describe. Use it to pick which\ncreative to bid on a given slot with.\n\nScoring is vector similarity over observed audience contexts, not a\nmodelled conversion or revenue estimate. `predicted_score` is a cosine\nsimilarity in `0..1` — a relative ordering signal, comparable **within**\none response and not across calls or across audiences.\n\n## Sparse and failed results both return 200 with an empty list\n\nA creative with no recorded play context is simply absent from the\nresult rather than scored zero, so `ranked_count` is routinely smaller\nthan the number of ids you sent. A backend failure is also reported as an\nempty list under a `200`. An empty `creatives` array therefore means\n\"no ranking available\", never \"these creatives are a poor match\" —\nfall back to your own ordering rather than dropping the creatives.\n\n`historical_plays` is the evidence count behind each score. Treat a score\nbacked by a handful of plays as weak.\n\nThis route requires the same DSP platform key as the inventory routes,\nholding the `inventory:read` scope, and shares their 200-per-minute\nbudget.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/audience/rank-creatives"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/bid","operation_id":"acceptBidResponse","summary":"Submit OpenRTB 2.6 bid response","description":"Submit a standard OpenRTB 2.6 bid response. Bids are validated against\ncreative blocklists and floor prices, then entered into the live waterfall\nauction (or scored-only in sandbox mode).\n\n**Each bid requires:**\n- `impid` — Impression ID from the `/adslots` bid request\n- `price` — CPM bid price in USD (must be > 0)\n- `adm` OR `nurl` — Either inline ad markup (VAST XML, HTML) or a win notice URL\n\nBids missing both `adm` and `nurl` are rejected with reason `missing_adm_and_nurl`.\nBids below the floor price are rejected with reason `below_floor` (the floor price\nand your bid price are included in the rejection for transparency).\n\n**Response includes per-bid granularity:** each bid is individually accepted or\nrejected, so a single bid response can have both accepted and rejected bids.\n\n**`nurl` macro contract (PR 4):** when your cached bid wins delivery, Trillboards\nfires a GET to your `nurl` URL with these macros expanded before the request\nleaves our edge:\n\n| Macro | Replaced with |\n| --- | --- |\n| `${AUCTION_ID}` | request id of the served opportunity |\n| `${AUCTION_BID_ID}` | the `bid.id` from your seatbid |\n| `${AUCTION_IMP_ID}` | the `bid.impid` |\n| `${AUCTION_SEAT_ID}` | your DSP seat |\n| `${AUCTION_AD_ID}` | the `bid.adid` |\n| `${AUCTION_PRICE}` | the clearing CPM (USD) |\n| `${AUCTION_CURRENCY}` | always `USD` today |\n\nThe nurl call is fire-and-forget on the server side — a 4xx from your\nendpoint never blocks the served VAST, but it WILL surface in our\npartner-recon reports as a delivery anomaly.\n\n**Rate limit:** 1,000 requests/minute (production), 100 requests/minute (sandbox).\n","agent_safe":false,"idempotent":false,"cost_tier":"paid","llm_example":null,"tags":["Bidding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/bid"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/campaigns","operation_id":"createCampaign","summary":"Create a campaign with targeting","description":"Creates a campaign linking an approved creative to targeting criteria. The API\nreturns matching screens based on your targeting, so you can choose which\nspecific screens to assign.\n\n**The creative must have `status: approved`** (passed AI moderation). Campaigns\ncannot be created with pending or rejected creatives.\n\n**Workflow:**\n1. Create campaign with targeting criteria (this endpoint)\n2. Review the `screens` array in the response\n3. Call `POST /campaigns/assign` with your selected `screen_ids`\n\nThe response includes up to 20 matching screens. If `matching_screens` exceeds\n20, use the `/adslots` endpoint with the same targeting filters to browse\nthe full inventory.\n\n**Rate limit:** 100 requests/minute.\n","agent_safe":false,"idempotent":false,"cost_tier":"paid","llm_example":null,"tags":["Campaigns"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/campaigns"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/campaigns/assign","operation_id":"assignToScreens","summary":"Place creative on specific screens","description":"Assigns an approved creative to specific screens. Creates screen allocations\nthat make the ad visible in each screen's content playlist.\n\n**Screens pick up new assignments within 30-60 seconds** via their content\nrefresh cycle. No manual intervention is needed on the screen side.\n\n**Scheduling:** Optionally provide date and time ranges to control when the\ncreative is shown. If omitted, the creative runs indefinitely during all hours.\n\n**Limits:** Maximum 100 screen IDs per call. For larger deployments, make\nmultiple calls. Screen IDs come from the `/adslots` or `/campaigns` response.\n\n**Rate limit:** 100 requests/minute.\n","agent_safe":false,"idempotent":false,"cost_tier":"paid","llm_example":null,"tags":["Campaigns"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/campaigns/assign"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/creatives","operation_id":"createCreative","summary":"Upload a creative for AI moderation","description":"Upload a creative asset for automatic Gemini AI moderation. Two upload modes\nare supported:\n\n**URL mode** — Provide a `creative_url` pointing to your hosted asset. The URL\nmust be publicly accessible. Trillboards downloads the asset and begins AI\nmoderation immediately.\n\n```json\n{\n  \"name\": \"Summer Sale 15s\",\n  \"content_type\": \"video/mp4\",\n  \"creative_url\": \"https://cdn.example.com/ads/summer-sale.mp4\"\n}\n```\n\n**Upload mode** — Get a presigned S3 URL for direct upload. Provide a `filename`\ninstead of `creative_url`. The response includes `upload.upload_url` — PUT your\nfile there with the specified Content-Type header, then call\n`POST /creatives/{id}/confirm` to trigger moderation.\n\n```json\n{\n  \"name\": \"Summer Sale 15s\",\n  \"content_type\": \"video/mp4\",\n  \"filename\": \"summer-sale.mp4\"\n}\n```\n\n**AI moderation** typically completes within 30 seconds and produces:\n- Content classification (8 safety flags)\n- MPAA-style age rating (G through NC-17)\n- Brand recognition\n- IAB Content Taxonomy 1.0 category codes\n- Content analysis (subjects, setting, mood, quality, keywords)\n\nPoll `GET /creatives/{id}` to check moderation status. Only creatives with\n`status: approved` can be used in campaigns.\n\n**Rate limit:** 100 requests/minute.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Creatives"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/creatives"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/creatives/{id}/confirm","operation_id":"confirmCreativeUpload","summary":"Confirm presigned upload and trigger moderation","description":"After uploading a creative via the presigned URL (upload mode), call this endpoint\nto confirm the upload and trigger AI moderation.\n\n**This endpoint is only needed for upload mode** (when you provided `filename`\ninstead of `creative_url` during creation). URL mode creatives are moderated\nautomatically.\n\nNo request body is required. The endpoint verifies the file exists in S3 and\nenqueues the AI moderation pipeline.\n\n**Rate limit:** 100 requests/minute.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creatives"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/creatives/<id>/confirm"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/deals/propose","operation_id":"proposeDeal","summary":"Propose a new deal","description":"Propose a new PMP, preferred, or guaranteed deal. Proposed deals are created in\n`pending` status and require admin approval before becoming active.\n\n**Required:** `deal_id` (your unique identifier) and `floor_cpm` (minimum CPM > 0).\n\n**Deal lifecycle:** pending -> active -> (paused | expired)\n\nYou will receive a webhook notification (if configured) when the deal status changes.\n\n**Rate limit:** 100 requests/minute.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Deals"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/deals/propose"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/discover","operation_id":"discover","summary":"Semantic inventory search","description":"Find screens using natural language queries. Uses **pgvector semantic embeddings**\nto match your query against scene descriptions generated by on-device vision\nlanguage models (VLMs).\n\n**Example queries:**\n- \"busy coffee shops with young professionals in Manhattan\"\n- \"gym lobbies with TVs near college campuses\"\n- \"high-end restaurants in Beverly Hills with bar seating\"\n- \"transit hubs with high foot traffic during rush hour\"\n\nResults are ranked by cosine similarity to your query embedding. Each result\nincludes screen details and FEIN signals (when available).\n\nOptionally provide a `budget` to receive delivery estimates alongside results.\n\n**Note:** When the semantic search backend (pgvector) is unavailable, the\nresponse is simplified — results are returned without similarity scores and\nthe `fein` object is omitted from each result. The endpoint still returns\nmatching screens based on keyword/filter fallback.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Inventory"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/discover"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/domedia/availability","operation_id":"getDomediaInventoryAvailability","summary":"Quote mapped DOmedia inventory availability","description":"DOmedia OpenDirect callback over the existing inventory and pricing owners.\nRequires a platform key owned by data_partner/domedia with inventory:read.\nProduct IDs are DOmedia asset IDs. UTC flight boundaries are continuous,\nstart-inclusive and end-exclusive. Price is USD for the available flight.\nAssets without positive available capacity are omitted. A missing canonical\nprice omits price without erasing known availability.\nThe existing data API limiter allows 60 authenticated requests per minute\nper partner; the callback shares that counter and its rate-limit response.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Inventory"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/domedia/availability"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/fein/content-semantic-search","operation_id":"contentSemanticSearch","summary":"Semantic similarity search over content library","description":"Accepts a natural language query, embeds it via the embedding service,\nand returns content_reviews ordered by cosine similarity using pgvector.\nRequires the content_reviews table to have embeddings populated.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Content Intelligence"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/fein/content-semantic-search"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/fein/webhooks","operation_id":"registerFeinWebhook","summary":"Register FEIN webhook","description":null,"agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["FEIN Webhooks"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/fein/webhooks"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/forecast","operation_id":"forecast","summary":"Estimate campaign delivery and spend","description":"Given targeting criteria and a budget, estimates how many impressions you can\nexpect, your likely win rate, and average CPM. Forecasts are computed from\n7 days of historical ClickHouse rollup data from matching screens.\n\n**Use this before creating a campaign** to understand expected delivery and\noptimize your budget allocation.\n\nThe `confidence` field indicates forecast reliability:\n- **high** — 50%+ of matching screens have 7+ days of historical data\n- **medium** — Some screens have data but coverage is incomplete\n- **low** — Insufficient historical data; estimates are rough projections\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Inventory"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/forecast"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/onboard","operation_id":"onboard","summary":"Register a new DSP","description":"Self-serve DSP registration. Recommended flow: self-mint one platform API\nkey in Earner Settings → Partner API, send it as `Authorization: Bearer <key>`, and this\nendpoint links that same key to the DSP seat and grants the DSP/MCP scopes.\nIt does not return or rotate the raw key in this flow.\n\nFor backwards compatibility only, a request with no API key still mints a\n`tb_dsp_*` key and shows it once. A supplied but invalid key is rejected; it\nnever falls back to minting a new credential.\n\n**Sandbox mode** provides full API access for integration testing. Bids are validated\nand scored but do not enter the live waterfall auction. Contact\ndevelopers@trillboards.com to upgrade to production.\n\nThe `seat_id` is your permanent OpenRTB buyer seat identifier. It must be unique\nacross all registered DSPs and cannot be changed after registration. Use a short,\nmemorable identifier like your company abbreviation.\n\n**Rate limit:** 5 requests per hour (applies to both successful and failed attempts).\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Onboarding"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/onboard"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/screens/audience","operation_id":"getScreenAudience","summary":"Bulk audience profiles for up to 50 screens","description":"Returns structured audience intelligence for the specified screens. Combines\nthree data sources with graceful degradation — any subset may be null without\nfailing the overall request:\n\n1. **Redis live signals** — fresh face count, attention, emotion, purchase intent,\n   and ad receptivity (10-30 s refresh).\n2. **VAS rolling averages** — Verified Attention Seconds, CPM tier, multiplier.\n3. **PostgreSQL venue enrichment** — neighborhood classification, Places rating,\n   amenity density, foot-traffic level.\n\n**Per-screen shape:**\n- `data_quality`: `live` when Redis has fresh data, `historical` when only\n  VAS is available, `none` when no audience data exists for the screen.\n- `signals_age_ms`: milliseconds since the last Redis update (null for non-live).\n- `demographics`, `behavior`, `venue` — filtered by `signal_types` if provided.\n- `vas` is always included (core DSP signal).\n- `patterns` is returned only when `include_patterns=true`. Currently reserved\n  for a future ClickHouse-backed hourly aggregation; returns `null` today.\n\n**Limits:** Maximum 50 screen IDs per request.\n\n**Rate limit category:** Inventory (200 req/min production).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screens"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/screens/audience"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/screens/availability","operation_id":"getScreenAvailability","summary":"Bulk capacity and booking query","description":"Returns per-screen deliverable capacity, booked impressions, and remaining\navailability for a given date range. Use this to confirm there is enough\ninventory to satisfy your flight before you bid or book.\n\n**What you get per screen:**\n- **Capacity** — Total impressions deliverable in the window, plus spots, hours,\n  and a forecast confidence (0-1).\n- **Booked** — Impressions already committed to active/scheduled placements.\n- **Available** — Remaining impressions and utilization percentage.\n- **Reliability** — Whether the screen is currently online and its last heartbeat.\n- **Daily breakdown** (optional) — Per-day capacity/booked/available when\n  `granularity=daily` is provided.\n\n**Caching:** 5-minute in-memory cache keyed by the hash of the screen ID list\nand date range. Serving from cache returns `X-Cache: HIT`.\n\n**Limits:** Maximum 50 screen IDs per request. Returns a 503 if the forecast\nservice is unavailable.\n\n**Rate limit category:** Inventory (200 req/min production).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screens"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/screens/availability"}},{"surface":"demand","method":"POST","path":"/openrtb/v2/webhooks","operation_id":"registerWebhook","summary":"Register a webhook subscription","description":"Registers a new webhook for your DSP seat. Returns a unique `whsec_...`\nsecret **exactly once** — store it immediately. The secret is used to\nverify the `X-Trillboards-Signature` header on every delivery.\n\n**URL must be HTTPS.** HTTP URLs, missing hostnames, and non-URL strings are\nrejected.\n\n**Events must be from the supported list.** See the `events` enum below; any\nunknown event name is rejected with a 400 listing the allowed values.\n\n**Limits:** Maximum 10 active webhooks per DSP seat. Exceeding this returns\n429 with the current count.\n\n**Rate limit category:** Campaigns (100 req/min).\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["DSP Webhooks"],"doc_url":"https://api.trillboards.com/docs#demand","example_request":{"method":"POST","path":"/openrtb/v2/webhooks"}},{"surface":"measurement","method":"DELETE","path":"/v1/advertiser/conversion-source/{sourceId}","operation_id":"deleteConversionSource","summary":"Disconnect a measurement source (soft delete)","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Measurement Sources"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"DELETE","path":"/v1/advertiser/conversion-source/<sourceId>"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}","operation_id":"getCampaignSummary","summary":"Campaign attribution summary","description":"Returns comprehensive attribution metrics for a campaign including reach,\nexposures, footfall visits, incremental lift, cost per visit, and statistical\nsignificance. This is the single endpoint that proves DOOH campaign ROI.\n\nThe response includes both raw metrics and quality indicators (match confidence,\ncontrol group size, significance level) so advertisers can assess data reliability.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Campaign Attribution"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/attention","operation_id":"getCampaignAttention","summary":"Attention metrics summary","description":"Returns attention-verified impression metrics from FEIN edge AI.\nThis is data that ONLY Trillboards can provide in DOOH.\n\nUnlike CTV viewability which uses probabilistic models (\"MRC standard\"\n= 50% of pixels visible for 2 seconds), Trillboards measures actual\nhuman attention via on-device computer vision:\n\n- **face_count**: How many people were actually in front of the screen\n- **attention_score**: What fraction were looking at the screen (gaze detection)\n- **dwell_seconds**: How long they stayed in view\n- **emotion_engagement**: Facial expression analysis (not recording, edge-only)\n- **aCPM**: Cost per 1,000 attention-verified impressions\n\nThese are MEASURED signals, not estimates.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Attention Metrics"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/attention"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/attention/creative","operation_id":"getCreativeAttentionComparison","summary":"Per-creative attention comparison","description":"Compares attention metrics across creatives within a campaign.\nAnswers: \"Which creative gets more attention AND more store visits?\"\n\nThis is the holy grail of DOOH optimization — correlating creative\nexecution with actual human attention and downstream conversion.\nNo other platform can show this because no other platform measures\nattention at the creative level.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Attention Metrics"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/attention/creative"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/attention/timeseries","operation_id":"getAttentionTimeseries","summary":"Attention metrics over time","description":"Returns daily attention quality metrics. Use this to detect\nattention fatigue: if attention_score drops over time while\nimpressions stay flat, the creative is wearing out and needs\nrefreshing.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Attention Metrics"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/attention/timeseries"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/audience-overindex","operation_id":"getAudienceOverindex","summary":"Demographic over-index","description":"Returns how the campaign's measured audience skews versus the\nsurrounding neighborhood (Census ACS × FEIN edge-AI fusion). An\nover-index above 1.0 means a segment appears more often at your screens\nthan the local baseline; above 1.5 is a significant targeting signal.\n\nAnswers: \"Does this campaign actually over-deliver the audience I'm\nbuying?\" Returns `data: null` when no over-index has been computed yet.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/audience-overindex"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/audience-profile","operation_id":"getAudienceProfile","summary":"DSP-consensus audience profile","description":"Returns per-screen audience profiles built from the live bid stream:\nDSP-consensus audience segments (each confirmed by 2+ demand partners),\na demand score, and the top advertiser categories bidding on each venue.\n\nAnswers: \"Who does the open market think my venues reach, and how hot is\ndemand for them?\" These signals are inherited from bid intelligence at\nzero measurement cost.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/audience-profile"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/causal-lift","operation_id":"getCampaignCausalLift","summary":"Per-segment causal lift","description":"Returns heterogeneous treatment-effect (causal lift) estimates broken\ndown by audience segment — age, gender, weather, daypart, brand\naffinity — computed daily via an X-learner model with bootstrap\nconfidence intervals.\n\nAnswers: \"The headline says my ads lifted visits — but WHICH audience\nactually moved?\" Surfaces the most- and least-responsive segments so\nbudget can shift toward the audiences the campaign genuinely persuades.\nComplements the aggregate chi-square result with the segment-level\nbreakdown. Every segment is returned with its exposed and control\ncounts.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Incrementality"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/causal-lift"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/channel-roas","operation_id":"getChannelROAS","summary":"Per-channel ROAS","description":"Returns return on ad spend (ROAS) broken down by channel.\nAnswers: \"DOOH vs mobile vs web — which channel pulls its weight?\"\n\nROAS = attributed_revenue / media_cost. A ROAS > 1.0 means the\nchannel generates more revenue than it costs. Use this to\njustify DOOH budget allocation vs digital channels.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Cross-Channel Attribution"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/channel-roas"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/creative-attribution","operation_id":"getCreativeAttribution","summary":"Creative-level attribution performance","description":"Returns attribution outcomes broken down by creative. Correlates\ncreative execution (attention, dwell, emotion) with downstream\noutcomes (store visits, lift, cost per visit).\n\nIncludes context interaction analysis: which creative performs best\nin which venue type, daypart, and weather condition.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Attribution"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/creative-attribution"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/creative-attribution/{creativeId}/timeseries","operation_id":"getCreativeAttributionTimeseries","summary":"Creative attribution over time","description":"Returns daily attribution metrics for a specific creative.\nUse this to detect creative fatigue: if attention degrades while\nimpressions stay constant, rotate the creative.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Attribution"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/creative-attribution/<creativeId>/timeseries"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/cross-channel-journey","operation_id":"getCrossChannelJourney","summary":"Cross-channel journey (Sankey flow)","description":"Returns flow data suitable for Sankey diagrams showing how users\nmove across channels after DOOH exposure.\n\nExample flow: Billboard (42%) -> Mobile Ad Click (28%) -> Website Visit (18%) -> Store Purchase (12%)\n\nEach node/link includes volume and conversion rate, enabling\nvisualization of the most common and most valuable paths.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Cross-Channel Attribution"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/cross-channel-journey"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/downtime-experiments","operation_id":"getDowntimeExperiments","summary":"Screen-downtime natural experiments (not measured)","description":"Screen-downtime natural experiments are NOT MEASURED. They took the\ndevices seen near a dark screen as a control group and conversion\nvisits as the outcome; the control group came from a PostgreSQL copy\nthat stopped taking writes, and conversion visits have no store. No\nexperiment was ever produced, so no published number is withdrawn.\n\nAlways answers zero experiments with no `avgLiftPct` (an average over\nno experiments is omitted, never reported as 0), and an `outcome` of\nkind `not_measured` saying why.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Incrementality"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/downtime-experiments"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/heatmap","operation_id":"getCampaignHeatmap","summary":"Geospatial exposure heatmap","description":"Returns geospatial clusters of exposure and conversion density.\nAnswers: \"WHERE are people seeing my ads and then visiting my stores?\"\n\nEach point represents a geographic cluster with exposure count,\nconversion count, and density. Use for map visualizations showing\ncampaign impact by location.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Campaign Attribution"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/heatmap"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/incrementality","operation_id":"getCampaignIncrementality","summary":"Incrementality results","description":"Returns Bayesian uplift measurement for a campaign. This is the\ngold standard of DOOH attribution — proving that store visits\nWOULD NOT have happened without the campaign.\n\nResults include:\n- **Bayesian posterior**: P(lift > 0) and 95% credible intervals\n- **Frequentist**: chi-square, p-value, confidence intervals\n- **Propensity quality**: standardized mean difference for covariate balance\n\nA P(lift > 0) > 0.95 indicates strong evidence of causal effect.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Incrementality"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/incrementality"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/journey","operation_id":"getCampaignJourney","summary":"Attribution journey funnel","description":"Returns the attribution funnel showing how exposed devices progress\nthrough the conversion journey: Exposure -> Visit -> Transaction.\n\nUse this to understand conversion rates at each stage and identify\nwhere the funnel leaks.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Campaign Attribution"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/journey"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/methodology","operation_id":"getMethodology","summary":"Attribution methodology explanation","description":"Returns a human-readable explanation of how attribution was computed\nfor THIS specific campaign. Covers exposure matching methodology,\nattention enrichment, incrementality measurement approach, and\ncross-channel attribution model.\n\nThis is RADICAL TRANSPARENCY — no other DOOH platform explains\ntheir methodology at the campaign level.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Signal Transparency"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/methodology"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/multi-touch","operation_id":"getMultiTouchAttribution","summary":"Multi-touch attribution summary","description":"Returns cross-channel credit allocation across DOOH, mobile, web,\nand store touchpoints. Supports multiple attribution models:\n\n- **time_decay**: Recent touchpoints get more credit (7-day half-life default)\n- **position_based**: First and last touch get 40% each, middle touches share 20%\n- **attention_weighted**: Credit proportional to FEIN attention score at each touchpoint\n- **shapley**: Game-theory-based fair value allocation (computationally intensive)\n\nThis answers: \"How much credit does DOOH deserve vs mobile vs web?\"\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Cross-Channel Attribution"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/multi-touch"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/optimizations","operation_id":"getOptimizations","summary":"Attribution-driven optimization weights","description":"Returns the current creative impression-share weights and venue/daypart\nbid modifiers computed from attribution outcomes, plus a human-readable\nexplanation and the campaign's auto-optimization settings.\n\nAnswers: \"Given what actually converted, where should my impressions and\nbids go?\" Creative weights are proportional to visit-rate × attention\nwith an exploration floor so no creative is fully suppressed; modifiers\nare capped at [-80%, +200%].\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Optimization"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/optimizations"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/roas","operation_id":"getRoasBreakdown","summary":"ROAS breakdown by channel and creative","description":"Returns headline ROAS with attributed revenue broken down by channel\n(DOOH, mobile, web) and by creative. Answers: \"Which channel and which\ncreative actually returned the spend?\"\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Transaction & ROAS"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/roas"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/signal-quality","operation_id":"getSignalQuality","summary":"Signal quality and coverage","description":"Returns signal quality metrics showing how complete and fresh the\nattribution data is for this campaign. This is radical transparency —\nno other DOOH platform exposes their data quality at the campaign level.\n\nUse this to assess confidence in attribution results:\n- High attention_coverage_pct = most impressions have FEIN verification\n- Low avg_signal_age_seconds = signals are fresh (real-time, not batch)\n- High bayesian_prob_positive = strong evidence of causal effect\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Signal Transparency"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/signal-quality"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/timeseries","operation_id":"getCampaignTimeseries","summary":"Daily attribution timeseries","description":"Returns daily attribution metrics over time. Use this to answer:\n\"Is my campaign's lift improving or degrading?\" and \"Which days drive\nthe most store visits?\"\n\nSupports date range filtering. If no dates specified, returns the full\ncampaign duration.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Campaign Attribution"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/timeseries"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/transactions","operation_id":"getTransactionAttribution","summary":"Transaction attribution & ROAS summary","description":"Returns purchase-matched attribution for a campaign: total and\nattributed transactions, attributed revenue, ROAS, iROAS, Bayesian\nsales lift (exposed vs control), and time-to-purchase timing.\n\nAnswers: \"Did the people who saw my ad actually buy — and was the media\nspend worth it?\" Transactions are ingested from your POS or\nserver-postback conversion source and matched to DOOH exposures within\na 14-day window using time-decay weighting.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Transaction & ROAS"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/transactions"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/transactions/timeseries","operation_id":"getTransactionTimeseries","summary":"Daily ROAS timeseries","description":"Returns daily transaction attribution and ROAS across the campaign.\nAnswers: \"Which days actually converted, and is my ROAS trending up or\ndown?\" Supports optional date-range filtering.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Transaction & ROAS"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/transactions/timeseries"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/venue-loyalty","operation_id":"getVenueLoyalty","summary":"Venue loyalty breakdown","description":"Returns the loyalty-tier breakdown of the exposed audience — drive-by,\noccasional, regular, and loyalist — with average visit frequency,\nloyal-device counts, and the preferred daypart.\n\nAnswers: \"Am I reaching one-time passers-by or the regulars who define\nthis venue?\" Loyalty is derived from repeat-exposure patterns over a\n90-day window.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/venue-loyalty"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/campaign/{campaignId}/weather-incrementality","operation_id":"getWeatherIncrementality","summary":"Weather-based incrementality (not measured)","description":"Weather-as-natural-experiment lift is NOT MEASURED. It compared visit\nrates on good- vs bad-weather days per DMA, and both of its inputs (the\nDMA split and the per-screen weather) were PostgreSQL copies that\nstopped taking writes, with no ClickHouse equivalent to join. It never\nproduced a result, so no published number is withdrawn.\n\nAlways answers `data: null` with an `outcome` of kind `not_measured`\nsaying why. `data: null` here does not mean \"not computed yet\": no\nanalysis runs.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Incrementality"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/campaign/<campaignId>/weather-incrementality"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/experiment/{experimentId}","operation_id":"getExperiment","summary":"Experiment status and results","description":"Returns experiment configuration, status, and results (if available).\nResults include both Bayesian posteriors and frequentist p-values.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Incrementality"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/experiment/<experimentId>"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/attribution/experiment/{experimentId}/power","operation_id":"getExperimentPower","summary":"Power analysis","description":"Returns the required sample size to detect a given minimum detectable\neffect (MDE) at specified significance and power levels.\n\nUse this before running an experiment to ensure you'll have enough\ndata to detect the effect you care about.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Incrementality"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/attribution/experiment/<experimentId>/power"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/conversion-source/{sourceId}","operation_id":"getConversionSource","summary":"Fetch one measurement source","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Measurement Sources"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/conversion-source/<sourceId>"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/conversion-source/{sourceId}/capability-url","operation_id":"getConversionSourceCapabilityUrl","summary":"Get the capability ingest URL for a provider that cannot send a header","description":"Returns a pasteable ingest URL for MMP sources whose provider cannot\nsend `X-Trill-Signature`. AppsFlyer's Push API accepts the\n`Authorization` header only and requires extra parameters to travel in\nthe URL, so an AppsFlyer advertiser has no other authenticated ingest.\n\n**The URL IS the credential.** Anyone holding it can send conversions\nfor this source — a capability authenticates the CALLER, not the\nPAYLOAD. Prefer the signed postback wherever the provider can send a\nheader.\n\nIts own endpoint rather than a field on the source read, because that\nread is redacted and cached; a live credential must not enter that\ncache. Minted on demand from the source's current secret, so rolling\nthe secret changes what this returns and invalidates the old URL.\n\nIssued for `mmp` sources only.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Measurement Sources"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/conversion-source/<sourceId>/capability-url"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/conversion-source/{sourceId}/deliveries","operation_id":"listConversionSourceDeliveries","summary":"What this source's provider pushed, and what we answered","description":"The last N inbound deliveries for a source, so \"my provider is not\nsending\" and \"you are dropping what I send\" can be told apart.\n\nBodies are redacted of device identifiers at WRITE time, not on read —\nredacting on read would mean the unredacted value had been stored. A\nredaction marker means the field was PRESENT; an absent value is stored\nabsent, because \"no device id sent\" and \"we did not keep it\" are\ndifferent diagnoses.\n\nA delivery that failed authentication has no source and is not listed\nhere.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Measurement Sources"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/conversion-source/<sourceId>/deliveries"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/conversion-sources","operation_id":"listConversionSources","summary":"List this advertiser's measurement sources","description":"Redacted, and served from a 60-second cache. Because the cache stores\nthe redacted shape, the redaction is an ALLOWLIST: a column not named\nin it never reaches a client or the cache.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Measurement Sources"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/conversion-sources"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/proof/.well-known/public-key","operation_id":"getPublicKey","summary":"Get public key for signature verification","description":"Returns the Ed25519 public key used to sign all v2 proofs.\nThird parties can use this key to independently verify signatures.\n\n**No authentication required.**\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Public Key"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/proof/.well-known/public-key"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/proof/campaigns/{campaignId}/audience-proof","operation_id":"getAudienceProofs","summary":"Get audience-enhanced proofs","description":"Returns per-screen impression counts for the campaign and one\naggregate signature over the roll-up.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Proofs"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/proof/campaigns/<campaignId>/audience-proof"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/proof/campaigns/{campaignId}/proof","operation_id":"getCampaignProofs","summary":"Get proofs for a campaign","description":"Returns cryptographically signed proofs for all completed impressions\nin the specified campaign. Supports cursor-based pagination for large datasets.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Proofs"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/proof/campaigns/<campaignId>/proof"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/proof/screens/{screenId}/proof","operation_id":"getScreenProofs","summary":"Get proofs for a screen","description":"Returns proofs for all completed impressions on the specified screen.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Proofs"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/proof/screens/<screenId>/proof"}},{"surface":"measurement","method":"GET","path":"/v1/advertiser/proof/signature-info","operation_id":"getSignatureInfo","summary":"Get signature format documentation","description":"Returns information about the signature format for integrators.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Public Key"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/advertiser/proof/signature-info"}},{"surface":"measurement","method":"GET","path":"/v1/attribution/pixel.js","operation_id":"getAttributionPixelScript","summary":"Web conversion pixel script","description":"Returns the Trillboards web conversion pixel as JavaScript. Embed it on an\nadvertiser site to report web conversions to `/v1/attribution/event/web`.\n\nUnauthenticated, `Access-Control-Allow-Origin: *`, cached for one hour.\nServed as `application/javascript; charset=utf-8` with\n`X-Content-Type-Options: nosniff` — **this endpoint does not return JSON.**\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/attribution/pixel.js"}},{"surface":"measurement","method":"GET","path":"/v1/attribution/pixel/{advertiserId}/{campaignId}","operation_id":"getTrackingPixel","summary":"Conversion tracking pixel","description":"Returns a 1x1 transparent GIF for web conversion tracking. Embed as:\n```html\n<img src=\"https://api.trillboards.com/v1/attribution/pixel/{advertiserId}/{campaignId}\" width=\"1\" height=\"1\" />\n```\n\nEach pixel fire creates a touchpoint in the cross-channel attribution\npipeline using a visitor ID (SHA-256 of IP + User-Agent).\nNo cookies, no JavaScript — pure server-side tracking.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/attribution/pixel/<advertiserId>/<campaignId>"}},{"surface":"measurement","method":"GET","path":"/v1/attribution/postback/{provider}/c/{token}","operation_id":"ingestCapabilityScopedMmpPostbackViaGet","summary":"Per-source MMP postback over GET, for providers that default to it","description":"The same ingest, the same capability, the same handler — with the event\nfields in the QUERY STRING instead of a JSON body.\n\nOffered because two of the five supported providers default to this\ntransport (measured from their published documentation 2026-09-19):\n\n- **Adjust** server callbacks default to GET. POST is a per-callback\n  setting the advertiser has to find and change.\n- **Kochava** documents its custom postback's data as key/value pairs\n  in the destination URL's query string and publishes no method\n  selector at all.\n\nWhile the postback routes were POST-only, an advertiser on either\nprovider's default configured a callback that 404'd — and a 404 on an\nunregistered method never reaches the inbound delivery log, so nothing\nrecorded it. The portal showed \"not sending yet\" for a postback the\nprovider believed it was delivering.\n\n**GET is offered ONLY on the capability path.** The signed route\nauthenticates an HMAC over the request body, and a GET has none, so\naccepting GET there would mean accepting an unsigned request on the one\nroute whose purpose is proving the payload. A capability authenticates\nthe caller, which is all any GET can do.\n\nRaw device identifiers sent this way (`idfa`, `gps_adid`,\n`advertising_id`, …) are stripped from the URL before anything logs it,\nand redacted out of the stored delivery body exactly as on the POST\npath.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/attribution/postback/<provider>/c/<token>"}},{"surface":"measurement","method":"GET","path":"/v1/attribution/provider-impression","operation_id":"ingestProviderImpressionViaGet","summary":"Provider impression callback (GET)","description":"Impression callback for an external ad provider, callable as a tracking-pixel\nURL. Identical semantics to the `POST` form; parameters are read from the\nquery string instead of the body.\n\nAuthentication is the HMAC-signed `t` parameter itself — there is no API key.\nVerification is constant-time and fails closed.\n\n> **This intake is disabled by default.** When the intake flag is off the\n> endpoint returns `404` without touching the database, so a `404` here means\n> \"not enabled\", not \"wrong URL\".\n\nFailures are deliberately never surfaced to the calling ad server: an\ninternal error returns the same empty `204` as success.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"GET","path":"/v1/attribution/provider-impression"}},{"surface":"measurement","method":"PATCH","path":"/v1/advertiser/conversion-source/{sourceId}","operation_id":"updateConversionSource","summary":"Update a measurement source","description":"A PATCH never rotates the stored webhook_secret — use the roll endpoint.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Measurement Sources"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"PATCH","path":"/v1/advertiser/conversion-source/<sourceId>"}},{"surface":"measurement","method":"POST","path":"/v1/advertiser/attribution/campaign/{campaignId}/cohort/export","operation_id":"exportCohort","summary":"Export cohort to DSP","description":"Exports the exposed audience cohort (SHA-256 hashed MAID list) to\na DSP for retargeting activation. Supported destinations:\n\n- **ttd**: The Trade Desk (1P data segment upload)\n- **dv360**: Google Display & Video 360 (Customer Match)\n- **meta**: Meta/Facebook (Custom Audiences)\n\nReturns a pre-signed S3 URL for the exported file and the MAID count.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Cohort Export"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/advertiser/attribution/campaign/<campaignId>/cohort/export"}},{"surface":"measurement","method":"POST","path":"/v1/advertiser/attribution/campaign/{campaignId}/cohort/refresh","operation_id":"refreshCohort","summary":"Force cohort re-sync to a DSP","description":"Forces an immediate re-sync of the campaign's exposed cohort to a DSP\ndestination. The raw encrypted-identifier cohort is routed through\nIntuizi, which resolves it and fans out to the requested DSP on their\nactivation graph.\n\nAnswers: \"Push my freshest exposed audience to the DSP now — don't wait\nfor the daily refresh.\" Returns 404 when no ready cohort exists yet.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Cohort Export"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/advertiser/attribution/campaign/<campaignId>/cohort/refresh"}},{"surface":"measurement","method":"POST","path":"/v1/advertiser/attribution/experiment","operation_id":"createExperiment","summary":"Create incrementality experiment","description":"Creates a new incrementality experiment with specified design:\n\n- **geo_holdout**: Withhold ads in control DMAs, compare visit rates\n- **ghost_ad**: Serve PSA ads to control group, measure true lift\n- **psa_control**: Serve public service announcements as control creatives\n- **matched_market**: Match treatment/control markets on covariates\n\nThe experiment runs automatically once created. Results populate\nin the experiment status endpoint as data accumulates.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Incrementality"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/advertiser/attribution/experiment"}},{"surface":"measurement","method":"POST","path":"/v1/advertiser/conversion-source","operation_id":"createConversionSource","summary":"Connect a measurement source","description":"Creates a measurement connection for the authenticated advertiser.\n\nThe response is ONE OF ONLY TWO places the full `webhook_secret` ever\nappears (the other is the roll). Every later read redacts it to\n`webhook_secret_last4`. A client-supplied secret is ignored by contract\n— it is generated server-side.\n\n`advertiser_mongo_id` is always the JWT owner and is never taken from\nthe body.\n\n**This create can turn an existing connection OFF.** One active source\nper (advertiser, `source_type`, `domain`) is the model, so a create\nREPLACES. An `mmp` source has a NULL domain, so a new provider displaces\nevery other MMP provider — connecting Adjust deactivates a live\nAppsFlyer source, whose postback URL then 404s. Whatever this call\ndeactivated comes back in `source.deactivated_sources`, and a client\nthat ignores it leaves the advertiser to discover the outage from their\nown failing postbacks.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Measurement Sources"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/advertiser/conversion-source"}},{"surface":"measurement","method":"POST","path":"/v1/advertiser/conversion-source/{sourceId}/roll-secret","operation_id":"rollConversionSourceSecret","summary":"Roll a source's signing secret","description":"Mints a new `webhook_secret` and keeps the OLD one valid for a bounded\noverlap — 24 hours by default, 1–72 configurable — so deliveries already\nin flight, and retries of events signed before the new value was pasted\nin, still verify.\n\nThe response is the only place the new secret appears.\n\nREFUSED, with a stated reason, where the secret is not ours to rotate.\nShopify verifies against the Trillboards app secret as well as the\nsource's own, so rolling would revoke nothing; a Square-backed POS\nwebhook signs with a key Square issued, and rolling it would break\nverification permanently. Only `mmp` and `server_postback` may roll.\n\nRolling also invalidates any capability URL issued for the source.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Measurement Sources"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/advertiser/conversion-source/<sourceId>/roll-secret"}},{"surface":"measurement","method":"POST","path":"/v1/advertiser/proof/campaigns/{campaignId}/export-proof","operation_id":"exportProofs","summary":"Export proofs in batch format","description":"Export proofs as JSON or CSV for offline verification.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Export"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/advertiser/proof/campaigns/<campaignId>/export-proof"}},{"surface":"measurement","method":"POST","path":"/v1/advertiser/proof/verify-proof","operation_id":"verifyProof","summary":"Verify a proof signature","description":"Verify the cryptographic signature of a proof.\nSupports both v1 (HMAC) and v2 (Ed25519) signatures.\n\n**No authentication required** - third parties can verify proofs independently.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Verification"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/advertiser/proof/verify-proof"}},{"surface":"measurement","method":"POST","path":"/v1/attribution/event/mobile","operation_id":"ingestMobileEvent","summary":"DSP mobile postback","description":"Receives mobile ad impression/click postbacks from DSP partners.\nThese events are ingested as touchpoints in the cross-channel\nattribution pipeline, enabling DOOH -> mobile -> conversion chains.\n\nNo authentication required — DSP postback URLs are typically\npre-configured and fire-and-forget.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/attribution/event/mobile"}},{"surface":"measurement","method":"POST","path":"/v1/attribution/event/transaction/{sourceId}","operation_id":"ingestTransactionEvent","summary":"Transaction data ingestion (source-scoped, signed)","description":"Ingests transaction/purchase data for conversion attribution from a\nPoint-of-sale or Server-postback conversion source. Sources include POS\nreceipts and backend purchase events.\n\nAuthentication is a per-source HMAC: sign the raw request body with the\nsource's `webhook_secret` and send it as\n`X-Trill-Signature: sha256=<hex>` (timing-safe, fail-closed). The\nadvertiser is taken from the source, never the body. A stable `eventId`\n(order id) makes retries exactly-once via the dedup ledger.\n\nTransaction data is matched to exposed devices via MAID hash\nto prove: \"This person saw the ad AND made a purchase.\"\n\n(The legacy unauthenticated bare `/v1/attribution/event/transaction`\nis retired and returns 410.)\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/attribution/event/transaction/<sourceId>"}},{"surface":"measurement","method":"POST","path":"/v1/attribution/event/web","operation_id":"ingestWebConversionEvent","summary":"Report a web conversion","description":"Ingests a web conversion observed by the conversion pixel. Unauthenticated;\nrate limited to 60 requests/minute per client.\n\n> **Success is not an acknowledgement that the event was stored.** A valid\n> request returns `204` immediately and ingestion continues asynchronously;\n> an internal ingest failure after validation still returns `204`. Do not\n> treat `204` as proof of durability, and do not retry on it.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/attribution/event/web"}},{"surface":"measurement","method":"POST","path":"/v1/attribution/postback/{provider}/{sourceId}","operation_id":"ingestSourceScopedMmpPostback","summary":"Per-source signed MMP postback","description":"Authenticated per-source MMP postback. Unlike the shared-secret routes,\nthis consumes the per-source `webhook_secret` the advertiser configures\nin their MMP dashboard, so the correct source flips to `receiving` and\nevents carry that source's advertiser. The provider must match the\nsource's configured `mmp_provider`.\n\nAuthentication is an HMAC-SHA256 over the RAW request body, sent as\n`X-Trill-Signature: sha256=<hex>` (timing-safe, fail-closed).\nProvider-native stable ids make retries exactly-once via the dedup\nledger; the response `deduped` flag reports when a retry was collapsed.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/attribution/postback/<provider>/<sourceId>"}},{"surface":"measurement","method":"POST","path":"/v1/attribution/postback/{provider}/c/{token}","operation_id":"ingestCapabilityScopedMmpPostback","summary":"Per-source MMP postback authenticated by a capability URL","description":"The same ingest as the signed per-source postback, for providers whose\nconfigurator cannot send a custom header.\n\nAppsFlyer's Push API accepts the `Authorization` header only, states\nthat custom headers are unsupported, and requires any extra parameter\nto travel in the URL — so an advertiser on AppsFlyer cannot satisfy the\n`X-Trill-Signature` contract at all. This route carries the whole\ncredential in the path instead.\n\n**This is weaker than the signed route and is offered only where that\none is impossible.** A capability authenticates the CALLER, not the\nPAYLOAD: anyone holding the URL can post conversions for that source,\nwhereas an HMAC over the body proves both. Prefer\n`/v1/attribution/postback/{provider}/{sourceId}` wherever the provider\ncan send a header.\n\nThe token is minted per source at\n`GET /v1/advertiser/conversion-source/{sourceId}/capability-url` and is\nsigned with that source's own `webhook_secret`, so it authorizes only\nthat source and **rolling the secret revokes it**. It does not expire.\nTreat the URL as a secret.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/attribution/postback/<provider>/c/<token>"}},{"surface":"measurement","method":"POST","path":"/v1/attribution/postback/adjust","operation_id":"ingestAdjustPostback","summary":"Adjust MMP postback","description":"Receives Adjust callbacks and ingests them as cross-channel touchpoints,\njoining mobile app conversions to DOOH exposures (raw device ids are\nhashed at the edge).\n\nAuthentication is an HMAC-SHA256 over the JSON request body, sent as\n`X-Signature: sha256=<hex>` (the `Authorization` header is also\naccepted), keyed by the shared `ADJUST_POSTBACK_SECRET`. Retries are\nde-duplicated via `event_token` + `adid` + `created_at`.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/attribution/postback/adjust"}},{"surface":"measurement","method":"POST","path":"/v1/attribution/postback/appsflyer","operation_id":"ingestAppsflyerPostback","summary":"AppsFlyer MMP postback","description":"Receives AppsFlyer install/event postbacks and ingests them as\ncross-channel touchpoints, joining mobile app conversions to DOOH\nexposures for the same device (raw device ids are hashed at the edge).\n\nAuthentication is an HMAC-SHA256 over the JSON request body, sent as\n`X-Signature: sha256=<hex>` (the `Authorization` header is also\naccepted), keyed by the shared `APPSFLYER_POSTBACK_SECRET`. Retries are\nde-duplicated via `event_id` (or `appsflyer_id` + `event_time`).\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/attribution/postback/appsflyer"}},{"surface":"measurement","method":"POST","path":"/v1/attribution/postback/kochava","operation_id":"ingestKochavaPostback","summary":"Kochava MMP postback","description":"Receives Kochava postbacks and ingests them as cross-channel\ntouchpoints, joining mobile app conversions to DOOH exposures (raw\ndevice ids are hashed at the edge).\n\nAuthentication is an HMAC-SHA256 over the JSON request body, sent as\n`X-Signature: sha256=<hex>` (the `Authorization` header is also\naccepted), keyed by the shared `KOCHAVA_POSTBACK_SECRET`. Retries are\nde-duplicated via `event_id`.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/attribution/postback/kochava"}},{"surface":"measurement","method":"POST","path":"/v1/attribution/provider-impression","operation_id":"ingestProviderImpression","summary":"Provider impression callback (POST)","description":"Impression callback for an external ad provider. See the `GET` form for the\nshared semantics — the intake flag, the HMAC `t` credential, and the\ndeliberate `204`-on-error behaviour all apply identically.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/attribution/provider-impression"}},{"surface":"measurement","method":"POST","path":"/v1/attribution/qr-signal","operation_id":"ingestQrBeaconSignal","summary":"Report QR scan device signals","description":"Receives device fingerprint signals for a QR scan, used to link a scan to an\nexposure. Unauthenticated; rate limited to 60 requests/minute per client.\n\nAccepts `application/json` and also `text/plain`, so it can be called with\n`navigator.sendBeacon()` — a plain-text body is parsed as JSON.\n\nReturns `202` as soon as the body validates; the signals are processed\nfire-and-forget, so a `202` does not mean the scan was successfully linked.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Event Ingestion"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"POST","path":"/v1/attribution/qr-signal"}},{"surface":"measurement","method":"PUT","path":"/v1/advertiser/attribution/campaign/{campaignId}/optimizations/auto","operation_id":"setAutoOptimize","summary":"Toggle auto-optimization","description":"Enables or disables automatic attribution-driven optimization for a\ncampaign, and optionally tunes the minimum sample size and exploration\nweight. When enabled, the nightly job recomputes creative weights and\nvenue/daypart bid modifiers from fresh attribution outcomes.\n\nAnswers: \"Let the attribution loop steer my impressions and bids\nautomatically.\"\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Optimization"],"doc_url":"https://api.trillboards.com/docs#measurement","example_request":{"method":"PUT","path":"/v1/advertiser/attribution/campaign/<campaignId>/optimizations/auto"}},{"surface":"partner","method":"DELETE","path":"/v1/partner/audience/segments/{segmentId}","operation_id":"deleteAudienceSegment","summary":"Delete audience segment","description":"Permanently deletes an audience segment.\nRequires usage-based billing (`requireBilling('data_api')`).\n","agent_safe":false,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Audience Segments"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"DELETE","path":"/v1/partner/audience/segments/<segmentId>"}},{"surface":"partner","method":"DELETE","path":"/v1/partner/device/{deviceId}","operation_id":"deleteDevice","summary":"Delete a device","description":"Soft-delete a device. It will no longer receive ads.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"DELETE","path":"/v1/partner/device/<deviceId>"}},{"surface":"partner","method":"DELETE","path":"/v1/partner/screen-groups/{groupId}","operation_id":"deleteScreenGroup","summary":"Delete (archive) a screen group","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"DELETE","path":"/v1/partner/screen-groups/<groupId>"}},{"surface":"partner","method":"DELETE","path":"/v1/partner/screen-groups/{groupId}/members","operation_id":"removeScreenGroupMembers","summary":"Remove members from a screen group","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"DELETE","path":"/v1/partner/screen-groups/<groupId>/members"}},{"surface":"partner","method":"DELETE","path":"/v1/partner/screens/{screenId}/default-streams/{streamId}","operation_id":"deletePartnerDefaultStream","summary":"Delete a default stream","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"DELETE","path":"/v1/partner/screens/<screenId>/default-streams/<streamId>"}},{"surface":"partner","method":"DELETE","path":"/v1/partner/sensing/routes/{routeId}","operation_id":"deletePartnerSensingRoute","summary":"Delete (soft) a Sense Anything Route","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"DELETE","path":"/v1/partner/sensing/routes/<routeId>"}},{"surface":"partner","method":"DELETE","path":"/v1/partner/team/{userId}","operation_id":"removeTeamMember","summary":"Remove a team member","description":"Remove a team member from the partner organization.\nThe owner cannot be removed.\n\n**Authorization:** Requires both partner API key AND user JWT with owner role.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Team"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"DELETE","path":"/v1/partner/team/<userId>"}},{"surface":"partner","method":"DELETE","path":"/v1/partner/webhooks/{webhookId}","operation_id":"deleteWebhook","summary":"Delete webhook","description":"Remove a webhook subscription.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Webhooks"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"DELETE","path":"/v1/partner/webhooks/<webhookId>"}},{"surface":"partner","method":"GET","path":"/v1/changelog","operation_id":"queryChangelog","summary":"Query API changelog","description":"Returns structured changelog entries parsed from CHANGELOG-API.md. Filter by date and change type. Unauthenticated, publicly cacheable.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":"Check what breaking changes were introduced since a given date: GET /v1/changelog?since=2026-04-01&type=breaking","tags":["API Meta"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/changelog"}},{"surface":"partner","method":"GET","path":"/v1/errors","operation_id":"listErrorCodes","summary":"List all error codes","description":"Returns the full error catalog as a JSON list. Each entry includes the stable code string, error type, HTTP status, human description, and doc URL.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":"Call this endpoint to enumerate every error code the API can return, so you can build a client-side error handler that covers all cases.","tags":["API Meta"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/errors"}},{"surface":"partner","method":"GET","path":"/v1/errors/{code}","operation_id":"getErrorCode","summary":"Look up a single error code","description":"Returns metadata for a single error code, including an example of the canonical error envelope the API returns when this code is triggered.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":"Look up a specific error code to understand what it means and see an example response envelope.","tags":["API Meta"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/errors/<code>"}},{"surface":"partner","method":"GET","path":"/v1/federated/models/{modelType}","operation_id":"federatedGetModelWeights","summary":"Fetch aggregated weights for the device-specific slice.","description":"Returns the most-specific aggregated weights matching the requested slice context. When the exact slice is empty, the server walks up the slice hierarchy (cold-start inheritance). When no parent slice is available, hard-coded defaults are returned with `isDefault=true`.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Federated Learning"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/federated/models/<modelType>"}},{"surface":"partner","method":"GET","path":"/v1/federated/models/{modelType}/latest","operation_id":"federatedGetLatestModelDescriptor","summary":"Fetch the latest aggregated model descriptor (download URL + sha256 + version).","description":"Returns metadata describing the latest aggregated model artifact for the requested slice. The client uses `downloadUrl` to pull the weights blob from object storage and `sha256` to verify integrity before applying. Inline `weights` may be returned for lightweight models.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Federated Learning"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/federated/models/<modelType>/latest"}},{"surface":"partner","method":"GET","path":"/v1/partner/ads/self-promo","operation_id":"listPartnerSelfPromoAds","summary":"List self-promo ads","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/ads/self-promo"}},{"surface":"partner","method":"GET","path":"/v1/partner/ads/self-promo/placements","operation_id":"listPartnerSelfPromoPlacements","summary":"List self-promo placements by screen","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/ads/self-promo/placements"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics","operation_id":"getAnalytics","summary":"Get partner analytics","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/audience-continuity","operation_id":"getAudienceContinuity","summary":"Visitor-flow continuity graph across the partner's screens","description":"Returns the top inter-screen audience paths (from-screen / to-screen,\nmedian lag, signals, confidence). Backed by\n`audienceContinuityService.getAudienceContinuity`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/audience-continuity"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/audience-profile","operation_id":"getAudienceProfile","summary":"Audience composition for the partner's screens","description":"Returns the audience-profile composition (demographic segments,\nage/gender distribution, lifestyle bands, confidence scores). Backed\nby `sceneIntelligenceService.getAudienceProfile`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/audience-profile"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/ble-summary","operation_id":"getBleSummary","summary":"BLE device discovery summary","description":"Returns `{ uniqueDevices, totalObservations }` aggregated across the\npartner's screens for `timeRange`. Backed by the same\n`conversationIntelligenceService.getBleDeviceSummary` the venue\nportal uses.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/ble-summary"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/content-performance","operation_id":"getContentPerformance","summary":"Content performance — ad lineage and measured audience reactions","description":"Returns the data shown in the venue-portal \"Content Performance\" modal —\nper-creative ad lineage (advertiser, brand, moderation status, IAB\nclassification, age rating, confidence, served-window) plus measured\naudience reactions (plays, attention, viewers, attentive-time) and the\ncreative-classification chip cloud (subjects, setting, mood, quality).\n\nBacked by the same `audienceMetricsService.getContentPerformance` that\npowers the venue portal — the partner endpoint just scopes the\nscreenIds to the partner's fleet.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/content-performance"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/creative-catalog","operation_id":"searchCreativeCatalog","summary":"Free-text search across the partner's creative catalogue","description":"Searches the partner's creative catalogue by advertiser name, brand,\ncreative title, or IAB category. Returns lightweight creative\ndescriptors (no audience metrics — use `/analytics/content-performance`\nfor those). Backed by the same `audienceMetricsService.searchCreativeCatalog`\nused by the venue portal.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/creative-catalog"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/creative-scenes","operation_id":"getCreativeScenes","summary":"Scene-level observations for a played creative","description":"Returns the timestamped scene observations captured while a specific\ncreative (`adId`) was playing on a specific screen (`screenId`). Each\nscene includes the audience-class chip cloud, attention metrics,\nengagement narrative, and the cloud-emitted `ad_fit_verdict`. Cursor-\npaginated. Backed by `audienceMetricsService.getCreativeSceneObservations`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/creative-scenes"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/cross-screen-flow","operation_id":"getCrossScreenFlow","summary":"Per-pair flow directionality + transition heatmap","description":"Returns the directional flow between every pair of partner screens\n(Sankey-friendly). Includes `edges` (from, to, flow_count, weight)\nand `screens` (position lookup). Backed by\n`crossScreenFlowService.getCrossScreenFlow`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/cross-screen-flow"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/dashboard","operation_id":"getPartnerAnalyticsDashboard","summary":"Unified portfolio dashboard (earnings, ads, audience, screens, insights)","description":"Returns the same unified dashboard payload the venue portal renders\nat `/v2/earner/analytics/dashboard`, scoped to the partner's fleet.\nDelegates to `earnerAnalyticsService.getDashboard` via the\npartner-context `screenIdsOverride` path so the shared service skips\nthe owner/manager lookup and operates on the pre-authorized screen\nlist. Earnings figures reflect the partner's revenue share where\napplicable.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/dashboard"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/device-suite-signature","operation_id":"getDeviceSuiteSignature","summary":"Device-type / OS / browser-class distribution among repeat visitors","description":"Returns the device-class breakdown observed across the partner's\nscreens — useful for ad-creative format/codec decisions and\npartner-audience targeting. Backed by\n`deviceSuiteSignatureService.getDeviceSuiteSignature`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/device-suite-signature"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/export","operation_id":"exportPartnerAnalytics","summary":"CSV export of partner-scope analytics (summary / daily / per-screen)","description":"Mirrors the venue-portal earner export at\n`/v2/earner/analytics/export`, scoped to the partner's fleet via\nthe `screenIdsOverride` path through\n`earnerAnalyticsService.getExportData`. Response body is the CSV\ntext directly; `Content-Disposition: attachment` is set so a\nbrowser will download instead of rendering.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/export"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/export/ads-by-day","operation_id":"exportPartnerAdsByDay","summary":"Per-ad daily breakdown for the partner fleet","description":"Returns one row per (date, screen, ad) for the partner's fleet\nacross the supplied date range. Reads from the live\n`ScreenDailyAnalyticsByAd` store scoped to the partner's screen\nlist (the aggregation cron is still Mongo-only; reading the PG\nmirror would silently empty recent days). Default response is\nCSV; `format=json` returns the structured rows for programmatic\nconsumption.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/export/ads-by-day"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/fleet-hourly","operation_id":"getFleetHourly","summary":"24-hour distribution across the partner's fleet","description":"Returns hourly visitor counts and average attention for each hour of\nthe day, aggregated across the partner's fleet for `timeRange`.\nBacked by `audienceMetricsService.getFleetHourlyBreakdown`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/fleet-hourly"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/halo-funnel","operation_id":"getHaloFunnel","summary":"4-stage discovery → awareness → engagement → conversion funnel","description":"Returns the partner-fleet funnel measured from venue audience\nsignals: stage counts, drop-off ratios, conversion signals.\nBacked by `haloFunnelService.getHaloFunnel`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/halo-funnel"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/loyalty-clock","operation_id":"getLoyaltyClock","summary":"24-hour returning-vs-first-timer audience clock","description":"Returns the 24-hour radial showing the share of returning vs first-\ntimer audience per hour over the `timeRange` window. \"Returning\" is\ndefined as the same resolved_device_id_hash seen on a different\ncalendar day within the lookback window. Backed by\n`loyaltyClockService.getLoyaltyClock`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/loyalty-clock"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/network-summary","operation_id":"getNetworkSummary","summary":"Fleet-wide audience summary for the partner's screens","description":"Returns the fleet-wide audience summary for the partner's screens\nfor the given `timeRange`. Equivalent to the non-live path of\n`GET /v2/earner/analytics/network-summary`. Backed by\n`audienceMetricsService.getNetworkSummaryByScreenIds`. The\nWebSocket-style live mode is not exposed to partners via this\nendpoint; partners poll the standard rollup.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/network-summary"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/network-trend","operation_id":"getNetworkTrend","summary":"Daily/hourly fleet-wide audience timeseries","description":"Returns a timeseries of fleet-wide visitor counts + average attention\nover `timeRange`, bucketed by `interval`. Backed by\n`audienceMetricsService.getNetworkTrend`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/network-trend"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/proximity-summary","operation_id":"getProximitySummary","summary":"Cross-screen proximity-graph summary","description":"Returns the same proximity-graph summary the venue-portal Insights\npane shows — `uniqueSignals`, `repeatSignals`, `crossScreenSignals`,\nper-source breakdown (BLE / WiFi / mDNS / gateway), distance-aware\ngate diagnostics, continuity edges, and top paths. Backed by\n`conversationIntelligenceService.getProximitySummary`. Every count is\nan aggregate over the partner's screen set.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/proximity-summary"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/revenue-attention-correlation","operation_id":"getRevenueAttentionCorrelation","summary":"Per-creative scatter of avg-attention vs revenue-per-impression","description":"Returns scatter-plot data showing the relationship between average\nattention and revenue per impression, point-per-creative, plus a\ntrend line. Backed by\n`audienceMetricsService.getRevenueAttentionCorrelation`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/revenue-attention-correlation"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/scene-feed","operation_id":"getSceneFeed","summary":"Paginated chronological feed of scene observations","description":"Returns a paginated time-ordered list of scene observations across\nthe partner's screens — timestamp, scene tags, face_count, confidence,\nthumbnail_url. Backed by `sceneIntelligenceService.getSceneFeed`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/scene-feed"}},{"surface":"partner","method":"GET","path":"/v1/partner/analytics/scene-intelligence","operation_id":"getSceneIntelligence","summary":"Latest scene description per screen","description":"Returns the most-recent scene description for each of the partner's\nscreens — venue-class chip cloud, dominant mood, occupancy band,\nambient signals. Backed by\n`sceneIntelligenceService.getLatestSceneDescriptionsWithDiagnostic`.\nReturns at most one row per screen.\n\n`scenes: []` is ambiguous on its own — it is returned both when the\nfleet genuinely has no recent scenes and when the read could not be\nperformed (deadline, PG read failure, dependency disabled). Read\n`sceneDiagnostic.measured` to tell the two apart; do NOT infer an\noutage from an empty array, and do not treat a `measured: false`\nresponse as evidence about the fleet.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/analytics/scene-intelligence"}},{"surface":"partner","method":"GET","path":"/v1/partner/audience-analytics","operation_id":"getAudienceAnalytics","summary":"Get partner-wide audience analytics","description":"Get aggregated audience analytics across all partner screens.\nProvides overview of impressions, active screens, and audience metrics.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/audience-analytics"}},{"surface":"partner","method":"GET","path":"/v1/partner/audience-export","operation_id":"exportAudienceData","summary":"Export audience data","description":"Export aggregated audience data for multiple screens.\nSupports JSON and CSV formats.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/audience-export"}},{"surface":"partner","method":"GET","path":"/v1/partner/audience/heatmap","operation_id":"getAudienceHeatmap","summary":"Get audience heatmap data","description":"Returns geospatial audience heatmap data across partner screens.\nAvailable to all tiers.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/audience/heatmap"}},{"surface":"partner","method":"GET","path":"/v1/partner/audience/live/{screenId}","operation_id":"getLiveAudience","summary":"Get live audience data for a screen","description":"Returns real-time audience data including face count, attention score, mood,\ndemographics, purchase intent, and crowd density. Available to all tiers.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/audience/live/<screenId>"}},{"surface":"partner","method":"GET","path":"/v1/partner/audience/lookalikes/{screenId}","operation_id":"getLookalikeScreens","summary":"Find lookalike screens","description":"Finds screens with similar audience profiles using vector similarity search.\nRequires usage-based billing (`requireBilling('data_api')`).\n","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/audience/lookalikes/<screenId>"}},{"surface":"partner","method":"GET","path":"/v1/partner/audience/predict/{screenId}","operation_id":"predictScreenAudience","summary":"Predict screen audience","description":"Predicts audience composition for a specific hour and day of week\nusing historical data. Requires usage-based billing (`requireBilling('data_api')`).\n","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/audience/predict/<screenId>"}},{"surface":"partner","method":"GET","path":"/v1/partner/audience/resolved-household-reach/{campaignId}","operation_id":"getResolvedHouseholdReach","summary":"Resolved household reach for a campaign","description":"Returns the unique household count reached by a campaign across the\nTrillboards network, with a confidence interval, audience composition\nbreakdown, and overlap with the partner's first-party audience (when\na `pseudo_id` list was supplied at campaign creation). Underpins\ncross-channel reach reporting.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/audience/resolved-household-reach/<campaignId>"}},{"surface":"partner","method":"GET","path":"/v1/partner/audience/segments","operation_id":"listAudienceSegments","summary":"List audience segments","description":"Returns all audience segments for the partner.\nRequires usage-based billing (`requireBilling('data_api')`).\n","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Audience Segments"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/audience/segments"}},{"surface":"partner","method":"GET","path":"/v1/partner/audience/segments/{segmentId}","operation_id":"getAudienceSegment","summary":"Get audience segment","description":"Returns a single audience segment by ID.\nRequires usage-based billing (`requireBilling('data_api')`).\n","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Audience Segments"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/audience/segments/<segmentId>"}},{"surface":"partner","method":"GET","path":"/v1/partner/audience/stats","operation_id":"getAudienceStats","summary":"Get audience data statistics","description":"Returns high-level statistics about audience data availability for the partner's screens.\nAvailable to all tiers.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/audience/stats"}},{"surface":"partner","method":"GET","path":"/v1/partner/billing/plans","operation_id":"listBillingPlans","summary":"List available partner billing plans","description":"Returns the catalogue of partner billing plans — tier name, monthly\nprice in USD cents, included quotas (ads, impressions, audience-API\ncalls), and per-unit overage rates.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/billing/plans"}},{"surface":"partner","method":"GET","path":"/v1/partner/billing/subscription","operation_id":"getBillingSubscription","summary":"Get the partner's current subscription","description":"Returns the partner's active subscription tier, billing cycle, next\ninvoice date, current-period usage roll-up, and the Stripe customer\nID. Use this to render an in-product billing-status UI.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/billing/subscription"}},{"surface":"partner","method":"GET","path":"/v1/partner/billing/usage","operation_id":"getBillingUsage","summary":"Get the partner's current-period usage","description":"Returns counters for impressions, audience-API calls, and ads served\nin the current billing period, plus overage projection vs. plan\nquota. Refreshed every five minutes from the rollup pipeline.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/billing/usage"}},{"surface":"partner","method":"GET","path":"/v1/partner/billing/usage-summary","operation_id":"getBillingUsageSummary","summary":"Get current billing period usage","description":"Returns committed primary-ledger balances and per-product usage for the\ncurrent UTC calendar month. Monetary cents and quantities are decimal\nstrings; preserve them as decimals rather than JavaScript floating point.\nperiod_end is exclusive. Saving a payment method or opening Checkout\ndoes not add credit: read this balance after verified payment settlement.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Billing"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/billing/usage-summary"}},{"surface":"partner","method":"GET","path":"/v1/partner/connect/dashboard","operation_id":"getConnectDashboard","summary":"Get partner dashboard overview","description":"Returns a comprehensive dashboard with screen counts, impression stats,\nearnings data, daily charts, and top-performing screens.\nOptimized for embedding in partner SDK dashboards.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Connect Dashboard"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/connect/dashboard"}},{"surface":"partner","method":"GET","path":"/v1/partner/connect/earnings","operation_id":"getConnectEarningsChart","summary":"Get earnings chart data","description":"Returns daily or weekly earnings breakdown for charting.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Connect Dashboard"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/connect/earnings"}},{"surface":"partner","method":"GET","path":"/v1/partner/connect/health","operation_id":"getConnectScreenHealth","summary":"Get screen health status","description":"Returns the online/offline status of all partner devices with heartbeat\ntimestamps, uptime percentages, and location data.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Connect Dashboard"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/connect/health"}},{"surface":"partner","method":"GET","path":"/v1/partner/content-presets","operation_id":"listContentPresets","summary":"List content presets","description":"List all available content preference presets.\nPresets provide pre-configured content filtering rules for common venue types.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/content-presets"}},{"surface":"partner","method":"GET","path":"/v1/partner/content-presets/{presetId}","operation_id":"getContentPreset","summary":"Get content preset","description":"Get details of a specific content preset.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/content-presets/<presetId>"}},{"surface":"partner","method":"GET","path":"/v1/partner/creative-experiments/{experiment_id}","operation_id":"getCreativeFeedbackExperiment","summary":"Read your native experiment, policy and latest 500 owned creative recovery records","description":"Read your resolved native buyer’s latest 500 generated/imported creative ledger records with explicit creatives_truncated. Same-buyer byte dedupe preserves original advertisement_mongo_id; linked_to_experiment_ad identifies current native membership. Moderation status and byte hash are present only when the global review URL exactly matches the ledger CDN URL. Brand review and global moderation remain separate. Generation/import committed retry receipts retain creativeId/renderKey so a lost response does not require another paid generation.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":"Read your native experiment, policy and latest 500 owned creative recovery records","tags":["Creative Feedback"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/creative-experiments/<experiment_id>"}},{"surface":"partner","method":"GET","path":"/v1/partner/creative-experiments/{experiment_id}/rewards","operation_id":"getCreativeFeedbackRewards","summary":"Read a metered page of latest measured native play states","description":"from/to are inclusive YYYY-MM-DD dates, at most 31 days. The cursor reads latest states and tombstones; a completed scan cursor rewinds for the next poll. Deduplicate by canonical_key and reward_revision_id. build_stamp and builder_version are provenance; a physical rebuild may retain the same logical revision. It does not freeze prior database versions. Each page requires a caller-owned idempotency_key and uses one existing data_api/api_calls operation; retries with an in-progress or committed key return 409, retaining the committed receipt in error.help.usage_receipt. gaze_share maps to gaze_pct_on_screen (0–1); gaze_overlap_ms maps to its overlap in milliseconds; dwell_ms maps to dwell_capped_ms_mean. unmeasured fields remain NULL.","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":"Read a metered page of latest measured native play states","tags":["Creative Feedback"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/creative-experiments/<experiment_id>/rewards"}},{"surface":"partner","method":"GET","path":"/v1/partner/creatives/{ref}","operation_id":"getPartnerCreative","summary":"Get a creative classification","description":"Read the classification for a creative you submitted, by its\n`sha256:<hex>` reference (or the bare 64-char hex). A partner may only\nread classifications for content it has submitted.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Moderation"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/creatives/<ref>"}},{"surface":"partner","method":"GET","path":"/v1/partner/dashboard","operation_id":"getDashboard","summary":"Get dashboard overview","description":"Get comprehensive dashboard overview including partner info, screen stats,\nearnings summary, top performing screens, and recent activity.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Dashboard"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/dashboard"}},{"surface":"partner","method":"GET","path":"/v1/partner/data-export/{dataset}","operation_id":"exportDataset","summary":"Export a partner-scoped dataset","description":"Export a partner-scoped dataset for the authenticated partner's screens, every\naggregate row with its counts. Partner screen scoping is enforced via\n`pgRepo.getEarnerScreensByPartnerMongoIds`, so a partner can never read another\npartner's rows.\n\nAvailable datasets:\n- `audience_metrics` — hourly audience aggregates (face count, attention, demographics, mood, weather)\n- `signal_audience` — hourly BLE, WiFi, and mDNS audience rollups (unique, returning,\n  and cross-screen devices, scan windows, dwell, and attention)\n- `attention_ledger` — per-impression Verified Attention Seconds (VAS) measurements\n- `impression_ledger` — settled completed impressions (vast_event / proof_of_play / partner_sdk)\n- `waterfall_events` — VAST waterfall request outcomes per ad opportunity\n- `intent_signals` — purchase / consideration / awareness signals\n- `audience_loyalty_features` — per-device cross-day loyalty features keyed by partner-scoped pseudonymous IDs\n  (`tb_` + the first 16 hex of HMAC-SHA256(the partner's salt, canonical device key)): one salt per partner,\n  whichever API key calls, so the ID is the same ref the partner's device-level files carry. Same physical\n  device produces a DIFFERENT ID for a different partner — partner-specific salt prevents cross-partner identity\n  correlation. Every identifiable radio source ships (BLE, mDNS, SSDP, HTTP probe and WiFi),\n  each row labelled in `_match_disclosure`. Master switch: the `AUDIENCE_LOYALTY_FEATURES_ENABLED`\n  runtime_config row (default true, ~5s propagation).\n- `screen_metadata` — per-screen inventory metadata: identity (`screen_id`, `unique_id`, `name`,\n  `status`, `online_status`, `last_seen`), location (`country`/`state`/`city`/`zip`,\n  `latitude`/`longitude`, `location_source` geocode provenance, `timezone`), venue classification\n  (`venue_category`/`venue_parent`/`venue_child`/`venue_subcategory`, `openooh_venue_type_id`,\n  `environment_type`, `venue_classification_source`), and projected-traffic / quality signals\n  (`projected_daily_impressions`, `places_rating`, `places_review_count`, `places_business_name`,\n  `foot_traffic_multiplier`, `commercial_value_score`, `floor_price_cpm`). This is the inventory\n  data partners hand to advertisers. No time dimension — `days` is ignored; paginate with\n  `limit`/`offset`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/data-export/<dataset>"}},{"surface":"partner","method":"GET","path":"/v1/partner/data-export/proximity-graph-edges","operation_id":"exportProximityGraphEdges","summary":"Export the cross-screen proximity graph edge list","description":"Returns the partner's cross-screen proximity-graph edges — pairs of screens, at least one of\nthem the partner's own, that the same device was seen moving between in the lookback window —\nevery edge with its counts: `edge_count` (distinct transitions), `unique_devices` and\n`lag_p50` (median minutes between the two sightings). Used by data-licensing buyers (DSP\naudience-modelling pipelines).\n\nA page at a time: the first page pins the window to end at its UTC second (`window_end`), and\nedges are ordered by `source_screen_id`, then `destination_screen_id`. Pass `next_cursor` back\nas `cursor`, with the same `days`, until `truncated` is `false`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/data-export/proximity-graph-edges"}},{"surface":"partner","method":"GET","path":"/v1/partner/data-export/summary","operation_id":"getDataExportSummary","summary":"List available datasets and per-dataset record counts","description":"Return per-dataset record counts and date ranges for all datasets exposed\nby `GET /data-export/{dataset}`. Restricted to screens owned by the\nauthenticated partner.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/data-export/summary"}},{"surface":"partner","method":"GET","path":"/v1/partner/data/geohash-audience-panel","operation_id":"getGeohashAudiencePanel","summary":"Geographic audience panel (data-licensing)","description":"Camera and radio activity per geohash-6 cell, UTC hour and registered venue category, every\nunit placed where its screen WAS: on the map-matched vehicle path at the instant, else at the\nscreen's last GPS trail fix (looking back 7 days), else, for a fixed screen, at its registered\nsite. A vehicle's registered coordinate is never used.\n\nDefinition 2 (2026-10-19). Source: `trillboards.geohash_activity_hourly`, one UTC day per build\n(the partner product `geohash_activity_hourly` v2 reads the same rows), each day at its newest\nbuild. Camera: face- and person-backed windows at their close, with faces, people in view, capped\ndwell and the head-pose index. Radio: the 6-minute slots of each screen at their midpoint, with the\nvisitor-source identifiers (BLE, mDNS, SSDP, HTTP probe) concurrent in each (summed:\n`visitor_key_slots`), split near field and far field at the pass-audience method's near-field\nboundary (`near_field_rssi_dbm`), the distinct identifiers of the cell-hour (`unique_devices`) and\ntheir pings (`observation_count`). A cell-hour no radio slot fell in (camera windows placed where no\nslot's midpoint was) carries its radio readings `null`: the radio was not read there. The approved\nmetric `radio_identifiers_per_person` (the in-cab panel's passenger near-field Bluetooth identifiers\nper person the camera read) converts identifiers of that population to people. Definition 1 filed\nevery radio ping under the screen's registry cell and carried no camera reading.\n\nEvery cell is returned with its measured counts, a page at a time: request pages with `limit` and\n`offset` until `truncated` is `false`; `next_offset` is the next page's `offset`. Rows are ordered by\n`(hour, geohash6, venue_category)`, which is unique per row. A day is rebuilt while its late camera\nwindows and trail fixes land: every page names the build it read (`built_at`), and pages that carry\ntwo builds are re-read from offset 0. A day the panel holds no build of is answered `404`\n`day_not_built`, never an empty page; an empty page is a built day whose filters matched no cell.\n\nDefault date is yesterday (UTC). Radio from 2026-05-24, camera from 2026-06-24.\n","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/data/geohash-audience-panel"}},{"surface":"partner","method":"GET","path":"/v1/partner/data/outcome-attribution","operation_id":"getOutcomeAttribution","summary":"Outcome-attribution export (data-licensing)","description":"Per `(creative, screen)` \"did the ad drive an outcome\" attribution over the partner's OWN\nscreens: how many EXPOSED devices RETURNED (and crossed screens) per creative — fusing sensor\nexposure × ad play × return-visit on the canonical device identity. Sensor outcome only;\ncarries no revenue.\n\nSource-of-record: the ClickHouse `ReplacingMergeTree` spine\n`trillboards.device_outcome_exposures`, populated from `device_exposure_events` joined to\n`completed_impressions` on screen + completed-at-within-exposure-window.\n\nEvery `(creative, screen)` cell is returned with its measured counts, a page at a time.\nThe first page pins the window to end on its UTC date (`window_end`), and rows are ordered\nby `creative_id`, then `screen_mongo_id`. Pass `next_cursor` back as `cursor`, with the\nsame `days`, `grain` and `creative_id`, until `truncated` is `false`: every page reads the\nsame window and the cells after the last one returned, so no page repeats or skips a cell\nwhile the table refreshes.\n","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/data/outcome-attribution"}},{"surface":"partner","method":"GET","path":"/v1/partner/device/{deviceId}","operation_id":"getDevice","summary":"Get device info","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/device/<deviceId>"}},{"surface":"partner","method":"GET","path":"/v1/partner/device/{deviceId}/ads","operation_id":"getAdsForDevice","summary":"Get ads for device","description":"**SDK Endpoint** - Returns ads for the specified device.\n\nThis endpoint is called by the Trillboards Lite SDK. It supports ETag caching\nfor SDK ad-catalog responses to minimize bandwidth usage and request fan-out.\nBrowser language is accepted through the optional `lang` query parameter. If\nomitted, the server uses the standard `Accept-Language` request header and\nemits its ISO-639-1 alpha-2 primary language in the bidder-facing request.\n\nIf header bidding is enabled for the screen, the response includes\n`header_bidding_settings` with dynamic VAST waterfall URLs for programmatic\nplayback. Dynamic waterfall catalog responses intentionally preserve the\nfive-minute SDK cache contract; clients fetch the returned VAST URLs when\nthey actually attempt playback. Server-side source health may calculate\nrequest-pressure decisions for first-party upstream fetches, but the\ncatalog keeps SQL/Admin-enabled demand sources eligible and visible.\n\nNo authentication required - device is identified by fingerprint or external device ID.\n\n**The response carries TWO independent demand rails.**\n\n**1. `data.ads[]` — DIRECT BOOKINGS ONLY. Never programmatic.**\n\nEverything in this array is a first-party booking against this specific screen:\na paid direct advertiser buy, a partner self-promo creative uploaded through the\npartner CMS, or the Trillboards instant-QR house ad. There is no DSP, no\nauction, and no bid in `ads[]`. If you are looking for programmatic demand and\nreading only this array, you will conclude we have none.\n\n**2. `data.header_bidding_settings.vast_waterfall.sources[]` — ALL programmatic\ndemand.**\n\nEvery DSP/SSP source lives here and nowhere else. **Walk the whole array in\n`priority` ASCENDING order** and stop at the first source that actually plays;\nadvance on every failure class (no-fill, empty VAST, zero-`<Ad>`, transport\nerror, per-source timeout, IMA load error, IMA playback error). `priority: 0`\nis real and valid, and gaps in the values are normal — treat `priority` as an\nopaque ascending sort key, never an index.\n\n`vast_tag_url` is **an alias of `sources[0].vast_url`**, present only for\nlegacy single-tag players. It is not \"the\" tag: a player that reads it and\nstops has skipped every other source in the waterfall. This is the single most\nexpensive integration bug we see.\n\nThe waterfall may also contain a `trillboards_direct` source. That is the same\ndirect booking as rail 1, bridged into VAST for BYO-IMA players — it is not\nprogrammatic demand.\n\n**Non-IMA / native players must fire their own tracking.**\n\nGoogle IMA SDK fires the VAST-embedded pixels for you. **A raw `<video>`\nwrapper, `video.js` without the VAST plugin, Shaka without the ads plugin, or\nany other custom player does not.** If you are not using IMA you must XML-parse\nthe VAST yourself and fire, per playback and exactly once each: every\n`<Impression>`, `<Tracking event=\"start\">`, the quartiles\n(`firstQuartile`/`midpoint`/`thirdQuartile`), `<Tracking event=\"complete\">`, and\n`<Error>` (with `[ERRORCODE]` substituted) on any load or playback failure.\nPreserve `<Error>` at every wrapper level of the chain.\n\nIf you skip this we have no record of the impression: no attribution, no\nearnings. Working `DOMParser` implementation and fire order:\n[Partner native IMA SDK guide §5b — If you're NOT using Google IMA SDK (raw VAST\nplayer)](https://github.com/trillboards/packages/blob/production/trillboard-api/docs/integrations/partner-native-ima-sdk.md#5b-if-youre-not-using-google-ima-sdk-raw-vast-player).\n\n**Testing the wire format.**\n\nAppend **`?test=1`** to force a guaranteed-fill test source into that one\nresponse (public alias `src_test`): a direct MP4 with no wrapper chain to\nresolve. No allowlist enrollment, per-request only — real devices polling\nwithout the flag are unaffected — and the creative is non-revenue. If `src_test`\ncompletes but real sources do not, your VAST handling is fine and the problem is\nin the partner response path.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Ads"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/device/<deviceId>/ads"}},{"surface":"partner","method":"GET","path":"/v1/partner/device/{deviceId}/heartbeat","operation_id":"getDeviceHeartbeat","summary":"Read live device heartbeat state","description":"Read-side companion to `POST /device/{deviceId}/heartbeat`. Returns\nthe device twin (Redis-cached, PG-fallback) so ad-serving partners\ncan check device health before issuing ad requests. Field-level\naccess control strips MDM compliance, vertex embeddings, identity-\ncorrection internals, partner identifiers, and Mongo `_id`. Pure\nread; no side effects.\n\nAuthenticated via the partner API key (`Authorization: Bearer\ntrb_partner_xxx` or `tb_prt_xxx`). Rate limit: 1000 req/min per\nAPI key.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/device/<deviceId>/heartbeat"}},{"surface":"partner","method":"GET","path":"/v1/partner/devices","operation_id":"listDevices","summary":"List all devices","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/devices"}},{"surface":"partner","method":"GET","path":"/v1/partner/docs","operation_id":"partnerDocsBrowser","summary":"Browsable HTML documentation index","description":"Returns an HTML page that renders the partner-API OpenAPI spec via\nRedoc, with a left-nav of every documented endpoint. Intended for\nhumans onboarding to the API; agentic clients should fetch the raw\nYAML at `/docs/openapi/partner-api.yaml` instead.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/docs"}},{"surface":"partner","method":"GET","path":"/v1/partner/earnings","operation_id":"getEarnings","summary":"Get earnings summary","description":"Get comprehensive earnings summary including lifetime earnings, current month,\navailable balance, breakdown by source, and payout information.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Earnings"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/earnings"}},{"surface":"partner","method":"GET","path":"/v1/partner/earnings/breakdown","operation_id":"getEarningsBreakdown","summary":"Get detailed earnings breakdown","description":"Get earnings breakdown grouped by day, week, or screen.\nUseful for detailed financial reporting and analysis.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Earnings"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/earnings/breakdown"}},{"surface":"partner","method":"GET","path":"/v1/partner/earnings/transactions","operation_id":"getTransactions","summary":"Get transaction history","description":"Get paginated list of earnings transactions and payouts.\nTransactions are aggregated by day showing impressions and calculated earnings.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Earnings"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/earnings/transactions"}},{"surface":"partner","method":"GET","path":"/v1/partner/events/connections","operation_id":"getActiveSSEConnections","summary":"List active SSE connections","description":"Returns the number of active SSE connections and their details.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Events"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/events/connections"}},{"surface":"partner","method":"GET","path":"/v1/partner/events/stream","operation_id":"subscribeToEvents","summary":"Subscribe to real-time events (SSE)","description":"Opens a Server-Sent Events (SSE) connection for real-time event streaming.\nMaximum 5 concurrent connections per partner.\n\n`creative.rewards_ready` carries a free readiness hint for an owned funded experiment.\nIts data uses `CreativeRewardsReadyEvent`: no reward rows are included. Follow the\ncanonical `reward_reader_url` with your own `idempotency_key`; that page uses the\nexisting data_api/api_calls tariff. Rewind the fixed date scope and deduplicate by\ncanonical play key plus reward revision. A hint is not a reward cursor or snapshot.\n\nSupports `Last-Event-ID` header for reconnection and missed event replay.\nThe server sends heartbeat comments (`:`) every 30 seconds to keep the connection alive.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Events"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/events/stream"}},{"surface":"partner","method":"GET","path":"/v1/partner/fleet-commands/{id}","operation_id":"getFleetCommand","summary":"Poll a fleet-command job","description":"Returns a fleet-command job created via POST /screen-groups/{groupId}/commands, including\nper-screen results. Gated by SCREEN_GROUPS_ENABLED.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Fleet Management"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/fleet-commands/<id>"}},{"surface":"partner","method":"GET","path":"/v1/partner/fleet/analytics","operation_id":"getFleetAnalytics","summary":"Get fleet analytics","description":"Returns fleet-wide performance metrics for the last 24 hours including\ntotal impressions, fill rate, average CPM, and attention score.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Fleet Management"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/fleet/analytics"}},{"surface":"partner","method":"GET","path":"/v1/partner/fleet/overview","operation_id":"getFleetOverview","summary":"Get fleet overview","description":"Returns a summary of all devices in the partner's fleet including\nonline/offline counts, venue type distribution, and country breakdown.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Fleet Management"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/fleet/overview"}},{"surface":"partner","method":"GET","path":"/v1/partner/impression","operation_id":"recordImpressionGet","summary":"Record impression (pixel)","description":"Record a single impression. Can be called as a tracking pixel (1x1 image load).\n\nBoth GET and POST methods are supported for flexibility.\nDuplicate `impid` + `adid` submissions are ignored (idempotent).\n\n**Optional Signature Verification**\nProvide signature headers to verify impressions:\n```\nX-Trillboards-API-Key: trb_partner_xxxxx\nX-Trillboards-Timestamp: <unix_seconds>\nX-Trillboards-Signature: sha256=<hmac>\n```\nSignature payload:\n```\n{timestamp}.{adid}.{impid}.{did}.{sid}.{aid}\n```\nSignature = HMAC-SHA256(api_key, payload)\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Impressions"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/impression"}},{"surface":"partner","method":"GET","path":"/v1/partner/info","operation_id":"getPartnerInfo","summary":"Get partner info","description":"Retrieve information about the authenticated partner","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/info"}},{"surface":"partner","method":"GET","path":"/v1/partner/intent/categories","operation_id":"listIntentCategories","summary":"List intent categories","description":"Returns the catalog of purchase intent categories with CPM multipliers\nand matching thresholds.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Intent Catalog"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/intent/categories"}},{"surface":"partner","method":"GET","path":"/v1/partner/intent/categories/{categoryId}","operation_id":"getIntentCategory","summary":"Get intent category details","description":"Returns detailed information about a specific intent category including triggers.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Intent Catalog"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/intent/categories/<categoryId>"}},{"surface":"partner","method":"GET","path":"/v1/partner/intent/pricing/{screenId}","operation_id":"getIntentPricing","summary":"Get intent-based pricing for a screen","description":"Returns real-time intent-based pricing for a specific screen based on\ncurrent audience signals (speech, emotion, behavior). Includes the detected\nintent category, purchase stage, and premium CPM.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Intent Catalog"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/intent/pricing/<screenId>"}},{"surface":"partner","method":"GET","path":"/v1/partner/measurement-methodology","operation_id":"getMeasurementMethodology","summary":"Trillboards measurement methodology disclosure","description":"Returns the versioned document describing how Trillboards captures\nout-of-home audience signal, resolves device identity, qualifies an\nexposure, and gates every published count. Written for buyer and\nprocurement due-diligence review; built against MRC OOH Phase 2.\n\nPublic — no authentication. Content-negotiated: an `Accept` header\npreferring `text/html` returns a rendered page, anything else returns\nthis JSON. Send `Accept: application/json`, or use\n`/measurement-methodology.json`, to pin the JSON from a browser.\n\n`version` changes whenever a disclosure changes and drives the `ETag`,\nso it is the canonical change-detector for polling clients.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/measurement-methodology"}},{"surface":"partner","method":"GET","path":"/v1/partner/measurement-methodology.json","operation_id":"getMeasurementMethodologyJson","summary":"Measurement methodology disclosure (JSON, no negotiation)","description":"Identical payload to `GET /measurement-methodology`, with the JSON\nrepresentation pinned. Exists so a browser — whose `Accept` header\nalways prefers `text/html` — can reach the machine-readable document\nwithout setting headers.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/measurement-methodology.json"}},{"surface":"partner","method":"GET","path":"/v1/partner/network-config","operation_id":"getNetworkConfig","summary":"Get network configuration","description":"Returns only partner-safe network controls. Internal demand names and\nseller configuration are never exposed. Runtime ad-delivery changes\napply to the next `/device/{deviceId}/ads` response; a response already\nin progress keeps its original timeout and source order.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Network Config"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/network-config"}},{"surface":"partner","method":"GET","path":"/v1/partner/network/trends","operation_id":"getNetworkTrends","summary":"Network-wide audience and inventory trends","description":"Returns daily-aggregate audience trends across the Trillboards\nnetwork — venue-type breakdowns, audience composition shifts,\ndevice-density changes — for the partner's licensed regions.\nNetwork-level aggregates; no per-screen rows.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Venue Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/network/trends"}},{"surface":"partner","method":"GET","path":"/v1/partner/ping","operation_id":"partnerPing","summary":"Unauthenticated liveness probe","description":"Returns a 200 with a tiny JSON payload identifying the partner-API\nservice. Used by load balancers, uptime monitors, and SDK install\nverification. No authentication required.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/ping"}},{"surface":"partner","method":"GET","path":"/v1/partner/ping/auth","operation_id":"partnerPingAuth","summary":"Authenticated liveness probe","description":"Returns a 200 with a JSON payload including the partner ID derived\nfrom the supplied bearer token. Used during partner onboarding to\nconfirm an API key is valid and to inspect the active tier.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/ping/auth"}},{"surface":"partner","method":"GET","path":"/v1/partner/pricing","operation_id":"getPricing","summary":"Get machine-readable pricing","description":"Returns full pricing information for all Trillboards products including\ngraduated usage tiers, free tier thresholds, committed-use discounts,\nand billing setup URLs. No authentication required — designed for\nprogrammatic price discovery by AI agents and DSPs.\n\nResponse is cached for 1 hour (Cache-Control: public, max-age=3600).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Billing"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/pricing"}},{"surface":"partner","method":"GET","path":"/v1/partner/sandbox/status","operation_id":"getSandboxStatus","summary":"Get sandbox status","description":"Returns current sandbox screens, event counts, and recent activity.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Sandbox"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/sandbox/status"}},{"surface":"partner","method":"GET","path":"/v1/partner/screen-groups","operation_id":"listScreenGroups","summary":"List screen groups","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screen-groups"}},{"surface":"partner","method":"GET","path":"/v1/partner/screen-groups/{groupId}","operation_id":"getScreenGroup","summary":"Retrieve a screen group","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screen-groups/<groupId>"}},{"surface":"partner","method":"GET","path":"/v1/partner/screen-groups/{groupId}/analytics","operation_id":"getScreenGroupAnalytics","summary":"Get analytics for a screen group","description":"Fleet analytics (last 24h) scoped to the group's member screens. Same shape as\nGET /fleet/analytics. Gated by SCREEN_GROUPS_ENABLED.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screen-groups/<groupId>/analytics"}},{"surface":"partner","method":"GET","path":"/v1/partner/screen-groups/{groupId}/content-policy","operation_id":"getScreenGroupContentPolicy","summary":"Get a screen group's content policy","description":"The typed content/moderation policy the group carries, or `null` when the\ngroup has none of its own. Member screens inherit it at read/serve time\n(screen ?? group ?? partner network default ?? system default). Gated by\nSCREEN_GROUPS_ENABLED.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screen-groups/<groupId>/content-policy"}},{"surface":"partner","method":"GET","path":"/v1/partner/screen-groups/{groupId}/members","operation_id":"listScreenGroupMembers","summary":"List a screen group's members","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screen-groups/<groupId>/members"}},{"surface":"partner","method":"GET","path":"/v1/partner/screen-groups/{groupId}/sensing-config","operation_id":"getScreenGroupSensingConfig","summary":"Get a screen group's sensing config","description":"The group's sensing profile binding + typed performance overrides, or\n`null` when the group has none. Member screens inherit via the sensing\nresolver's group step when they have no screen-specific deployment. Gated\nby SCREEN_GROUPS_ENABLED.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screen-groups/<groupId>/sensing-config"}},{"surface":"partner","method":"GET","path":"/v1/partner/screen/{external_code}/audience-segments","operation_id":"getScreenAudienceSegments","summary":"OpenRTB SDA payload for a screen","description":"Returns the OpenRTB 2.6 Supply-Side Audience (SDA) payload for the\nscreen, indexed by the partner-issued `external_code`. The payload\nencodes segment IDs from IAB Audience Taxonomy 1.1 and the\nTrillboards segtax=600 namespace, with current confidence values.\nBidders use this to apply per-screen targeting decisions.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screen/<external_code>/audience-segments"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/analytics","operation_id":"getScreenAnalytics","summary":"Get per-screen analytics","description":"Get detailed analytics for a specific screen including impression trends,\ndaily timeline, and performance metrics over a configurable period.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Dashboard"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/analytics"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/analytics/atmosphere","operation_id":"getPartnerScreenAtmosphere","summary":"Current atmosphere (mood, noise, crowd density)","description":"Returns the current venue atmosphere read for a screen — mood,\nnoise level, crowd density, current viewers, engagement. Tries\nlive data first, falls back to last hour of Postgres data.\nSame backing service as the earner endpoint.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/analytics/atmosphere"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/analytics/intelligence","operation_id":"getPartnerScreenIntelligence","summary":"Time-bucketed venue intelligence with Lincoln-Petersen dedup","description":"Returns time-bucketed audience intelligence for a screen with\nLincoln-Petersen multi-screen dedup. Same backing service as the\nearner endpoint (`venueAnalyticsService.computeScreenIntelligence`);\nthe partner controller fetches the screen row so the service can\ndo its sibling-screen lookup the same way the earner does.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/analytics/intelligence"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/analytics/taste-profile","operation_id":"getPartnerScreenTasteProfile","summary":"Per-screen taste profile + top-5 creative categories","description":"Returns the screen's FEIN Phase 6 taste profile — sample count,\nvenue type, daypart distribution, computed-from window, plus a\ntop-5 projection of creative categories that have played on this\nscreen recently. Same backing service as the earner endpoint\n(`screenTasteAnalyticsService.computeScreenTasteProfile`). Returns\n`ready: false` if the snapshotter hasn't filled the screen yet.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/analytics/taste-profile"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/analytics/traffic","operation_id":"getPartnerScreenTraffic","summary":"Hourly foot-traffic patterns for a screen","description":"Returns the per-hour foot-traffic histogram for a single screen\n(avg viewers, occupancy band, measurement count) plus a 0-10\nbusy-score. Same shape as\n`GET /v2/earner/screens/{screenId}/analytics/traffic` because both\nsurfaces delegate to `venueAnalyticsService.computeScreenTraffic`\n(extracted in PR #5265).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/analytics/traffic"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/analytics/vas","operation_id":"getPartnerScreenVAS","summary":"Verified Attention Seconds + CPM tier for a screen","description":"Returns VAS summary statistics (weighted average, median, p90, face\ncount, attention score, measurement totals) plus the screen's\ncurrent CPM tier and tier multiplier. Same backing service as the\nearner endpoint (`venueAnalyticsService.computeScreenVAS`).\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/analytics/vas"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/audience-metrics","operation_id":"getAudienceMetrics","summary":"Get real-time audience metrics","description":"Get real-time audience metrics for a screen including face detection,\nattention tracking, and demographic signals from the Troveworks AI pipeline.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/audience-metrics"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/content-preferences","operation_id":"getContentPreferences","summary":"Get screen content preferences","description":"Get content filtering preferences for a specific screen.\nControls what types of ads can be displayed.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/content-preferences"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/default-streams","operation_id":"listPartnerDefaultStreams","summary":"List default streams for a screen","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/default-streams"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/demographics","operation_id":"getScreenDemographics","summary":"Get screen demographics","description":"Get demographic summary for a screen including age distribution,\ngender split, income profile, and lifestyle segments.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Analytics"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/demographics"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/display-settings","operation_id":"getDisplaySettings","summary":"Get display settings","description":"Get display configuration for a screen including orientation,\nad surfaces, and playback policy.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/display-settings"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/l-bar","operation_id":"getPartnerLBarSettings","summary":"Get L-Bar settings","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/l-bar"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/programmatic-settings","operation_id":"getProgrammaticSettings","summary":"Get programmatic settings","description":"Get programmatic/header bidding settings for a screen.\nControls Google IMA, Prebid, and other programmatic ad sources.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/programmatic-settings"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/resolved-content-policy","operation_id":"getResolvedContentPolicy","summary":"Resolve the effective content policy for a screen","description":"Introspection (\"why\"): the EFFECTIVE content policy for a screen and its\nprovenance. Whole-layer coalescing — the effective policy comes from\nexactly ONE layer (screen ?? group ?? partner network default ?? system\ndefault). Read-only preview. Gated by SCREEN_GROUPS_ENABLED.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/resolved-content-policy"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/resolved-sensing-config","operation_id":"getResolvedSensingConfig","summary":"Resolve the effective sensing config for a screen","description":"Introspection: the EFFECTIVE sensing view for a screen and its provenance\n(screen deployment ?? group sensing config ?? default). Read-only preview.\nGated by SCREEN_GROUPS_ENABLED.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/resolved-sensing-config"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/sensing/chips","operation_id":"getScreenSensingChips","summary":"Read the custom sensing chips a screen is producing","description":"Returns the custom observation chips emitted by the screen's active Sense\nAnything profile(s). The returned field set is derived from the screen's\nprofile (the fields you defined via interpret-intent), bounded to custom\nprovenances. Scoped to a screen the authenticated partner owns.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/sensing/chips"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/{screenId}/sensing/settings","operation_id":"getScreenSensingSettings","summary":"Read a screen's AI sensing performance settings","description":"Returns the screen's desired/effective sensing performance mode, active\ncadence, read-only device capability telemetry, safety warnings, and the\nlast applied sensing profile. Device hardware facts are telemetry-derived\nand cannot be overwritten through this API.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/<screenId>/sensing/settings"}},{"surface":"partner","method":"GET","path":"/v1/partner/screens/resolve","operation_id":"resolvePartnerScreen","summary":"Resolve screen ID by device identifier","description":"Resolve a partner screen using a device identifier (fingerprint, device_id, or external_device_id).\nUse this when you only have device IDs but want to call screen-based CMS endpoints.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/screens/resolve"}},{"surface":"partner","method":"GET","path":"/v1/partner/sensing/routes","operation_id":"listPartnerSensingRoutes","summary":"List your Sense Anything Routes","description":"List the Routes owned by the authenticated partner.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/sensing/routes"}},{"surface":"partner","method":"GET","path":"/v1/partner/sensing/routes/{routeId}","operation_id":"getPartnerSensingRoute","summary":"Get one Sense Anything Route","description":null,"agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/sensing/routes/<routeId>"}},{"surface":"partner","method":"GET","path":"/v1/partner/stripe/login-link","operation_id":"getStripeLoginLink","summary":"Get Stripe dashboard login link","description":"Returns a temporary link to the partner's Stripe Express dashboard.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Stripe Connect"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/stripe/login-link"}},{"surface":"partner","method":"GET","path":"/v1/partner/stripe/onboarding-link","operation_id":"getStripeOnboardingLink","summary":"Get Stripe onboarding link","description":"Returns a temporary Stripe-hosted onboarding URL where the partner can\ncomplete identity verification and bank account setup.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Stripe Connect"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/stripe/onboarding-link"}},{"surface":"partner","method":"GET","path":"/v1/partner/stripe/payouts","operation_id":"getStripePayoutHistory","summary":"Get payout history","description":"Returns a paginated list of past Stripe payouts for the partner.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Stripe Connect"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/stripe/payouts"}},{"surface":"partner","method":"GET","path":"/v1/partner/stripe/status","operation_id":"getStripeAccountStatus","summary":"Get Stripe account status","description":"Returns the current status of the partner's Stripe Connect account including\nonboarding requirements and payout readiness.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Stripe Connect"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/stripe/status"}},{"surface":"partner","method":"GET","path":"/v1/partner/team","operation_id":"listTeamMembers","summary":"List team members","description":"List all team members for the partner organization.\nReturns member info, roles, and permissions.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Team"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/team"}},{"surface":"partner","method":"GET","path":"/v1/partner/team/invite/{token}","operation_id":"getInvitationByToken","summary":"Get invitation details","description":"Get details about an invitation by token.\nThis is a public endpoint used during the signup flow.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Team"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/team/invite/<token>"}},{"surface":"partner","method":"GET","path":"/v1/partner/tracking/.well-known/public-key","operation_id":"getTrackingPublicKey","summary":"Get public key for proof verification","description":"Returns the Ed25519 public key used to sign impression proofs.\n\n**No authentication required.** This is a public endpoint to enable third-party verification.\n\n**Verification Process:**\n1. Fetch public key from this endpoint\n2. Reconstruct canonical payload: `v2.{timestamp}.{adId}.{impressionId}.{screenId}.{deviceId}`\n3. Verify Ed25519 signature using the public key\n\nSee `/tracking/batch` response for signature format.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Batch Tracking"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/tracking/.well-known/public-key"}},{"surface":"partner","method":"GET","path":"/v1/partner/usage","operation_id":"getUsageSummary","summary":"Get API usage summary","description":"Returns API usage statistics including total calls, error rates,\ntop endpoints, and daily breakdown. Default range is 30 days, maximum 90 days.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Usage"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/usage"}},{"surface":"partner","method":"GET","path":"/v1/partner/usage/dashboard","operation_id":"getUsageDashboard","summary":"Get real-time usage dashboard","description":"Returns a comprehensive usage dashboard with today's stats, rate limit status,\nand last 7 days breakdown. Combines real-time Redis data with historical MongoDB data.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Usage"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/usage/dashboard"}},{"surface":"partner","method":"GET","path":"/v1/partner/usage/rate-limit","operation_id":"getRateLimitStatus","summary":"Get rate limit status","description":"Returns the partner's billing-tier rate limit metadata and their current\nobserved call rate.\n\n> **The `max_rate` in this response is billing-tier metadata, not the limit the\n> server enforces.** Enforcement for authenticated partner endpoints is a flat\n> **1000 requests/minute per API key**, applied identically to every tier. A\n> `basic` partner is therefore *not* throttled at 200/min, and an `enterprise`\n> partner does not get 5000/min. Size your client against 1000/min and treat\n> `429` + `Retry-After` as the only authoritative signal.\n\n`max_rate` is resolved from the partner's effective tier, which is derived from\nbilling status (`enterprise` when pay-per-use billing is configured, otherwise\n`basic`); the `tier` field echoes the stored `data_access_tier` separately.\n\n| Tier | Reported `max_rate` | Actually enforced |\n|------|--------------------|-------------------|\n| Basic | 200/min | 1000/min |\n| Developer | 1,000/min | 1000/min |\n| Enterprise | 5,000/min | 1000/min |\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Usage"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/usage/rate-limit"}},{"surface":"partner","method":"GET","path":"/v1/partner/vast/config","operation_id":"getVastConfig","summary":"Get VAST configuration for direct Google integration","description":"Returns a VAST tag URL template that partners use to fetch ads directly from Google Ad Manager.\n\n**This is NOT a VAST proxy.** Trillboards provides the configuration; partners fetch VAST directly from Google.\n\n**Architecture:**\n1. Partner calls this endpoint (cached for 1 hour)\n2. Partner builds VAST URL using the template\n3. Partner fetches VAST directly from Google (`pubads.g.doubleclick.net`)\n4. Partner plays video in their player\n5. Partner reports impressions via `/tracking/batch`\n\n**Scale:** At 35,000 screens with 1-hour cache, this is ~10 requests/second (trivial load).\n\n**Security:** Partners only allowlist 2 domains: `api.trillboards.com` and `pubads.g.doubleclick.net`.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["VAST Integration"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/vast/config"}},{"surface":"partner","method":"GET","path":"/v1/partner/venues/{screenId}/atmosphere","operation_id":"getVenueAtmosphere","summary":"Real-world atmosphere signals for a venue","description":"Returns aggregated atmosphere signals captured on-device at the\nscreen — ambient noise level, lighting band, motion intensity,\ndominant ambient class (cafe / lobby / outdoor / gym / retail). All\nsignals are venue-level aggregates.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Venue Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/venues/<screenId>/atmosphere"}},{"surface":"partner","method":"GET","path":"/v1/partner/venues/{screenId}/traffic","operation_id":"getVenueTraffic","summary":"Foot-traffic profile for a venue","description":"Returns daily and hourly foot-traffic histograms for the screen,\nderived from on-device face counts and dwell times. Includes\nrolled-up busiest-hour, average-dwell, and weekday-vs-weekend\nbreakdowns.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Venue Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/venues/<screenId>/traffic"}},{"surface":"partner","method":"GET","path":"/v1/partner/venues/{venueId}/intelligence","operation_id":"getVenueIntelligence","summary":"Get venue intelligence report","description":"Returns aggregated venue intelligence including multi-screen deduplication,\ntimeseries data with demographics, weather context, and audience metrics.\nMaximum date range is 30 days.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Venue Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/venues/<venueId>/intelligence"}},{"surface":"partner","method":"GET","path":"/v1/partner/venues/nearby","operation_id":"getVenuesNearby","summary":"List nearby Trillboards venues","description":"Returns venues within `radius_km` of `lat`/`lng`, scoped to venues\nthe partner has access to. Used for context-aware ad targeting and\nplace-of-interest analytics.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Venue Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/venues/nearby"}},{"surface":"partner","method":"GET","path":"/v1/partner/webhooks","operation_id":"listWebhooks","summary":"List webhooks","description":"Get all webhook subscriptions for your account.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Webhooks"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/webhooks"}},{"surface":"partner","method":"GET","path":"/v1/partner/webhooks/{webhookId}/deliveries","operation_id":"getWebhookDeliveries","summary":"Get delivery history","description":"Get recent webhook delivery attempts for debugging.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Webhooks"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/partner/webhooks/<webhookId>/deliveries"}},{"surface":"partner","method":"GET","path":"/v1/requests/{request_id}","operation_id":"getRequestMetadata","summary":"Look up metadata for a recent API request","description":"Returns metadata about a recent API request made with the caller's API key. Backed by an in-memory cache with ~7 minute lookback. Returns 404 if the request ID is unknown, expired from cache, or belongs to a different API key.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":"After receiving an error response, call GET /v1/requests/{request_id} with the request_id from the error envelope to retrieve timing and error classification.","tags":["API Meta"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v1/requests/<request_id>"}},{"surface":"partner","method":"GET","path":"/v2/csi/venue/{screenId}/history","operation_id":"csiVenueHistory","summary":"Historical venue occupancy in 10-minute CSI buckets.","description":"Returns CSI occupancy aggregated into 10-minute buckets over a look-back window (default 24 hours, max 168 hours / 7 days). Each bucket carries the average and peak occupant counts, the average motion score, and the number of CSI windows aggregated. Buckets are ordered most-recent-first. Requires the `sdk:csi` API-key scope.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CTV SDK & CSI"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v2/csi/venue/<screenId>/history"}},{"surface":"partner","method":"GET","path":"/v2/csi/venue/{screenId}/occupancy","operation_id":"csiVenueOccupancy","summary":"Real-time venue occupancy from WiFi CSI sensing.","description":"Returns the most recent CSI occupancy window (within the last 2 minutes) for a screen, derived from ESP32-S3 WiFi Channel State Information. The response is Redis-cached for 30s to support high-frequency polling. When an active window exists the body carries occupantCount, motionScore, signalQuality, hardwareType, the window bounds, and dataAge; otherwise it returns `status: \"no_data\"` with a human-readable message. Requires the `sdk:csi` API-key scope.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CTV SDK & CSI"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v2/csi/venue/<screenId>/occupancy"}},{"surface":"partner","method":"GET","path":"/v2/device/ota/manifest","operation_id":"deviceOtaManifest","summary":"Signed OTA manifest for managed-device update clients.","description":"Returns an Ed25519-signed manifest describing the set of agent APKs available for over-the-air delivery. The `agent` query parameter filters to a specific agent type; when omitted the caller receives the full bundle.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Device Provisioning"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v2/device/ota/manifest"}},{"surface":"partner","method":"GET","path":"/v2/earner/cameras/sensing-profile","operation_id":"deviceSensingProfile","summary":"Active sensing profile for the calling connector's bound screen.","description":"The BYO-CCTV connector polls this per feed to learn which fields its on-device VLM must emit, reaching parity with the cloud VLM's profile-driven schema. The screen is taken from the presented device token, never from the request, so a caller can only ever read its own screen. RTSP credentials and internal profile config never leave the server. A screen with no deployed profile — and a transient profile-read failure — both answer 200 with an empty profile, so sensing degrades to the connector's default schema instead of going silent.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Device Provisioning"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"GET","path":"/v2/earner/cameras/sensing-profile"}},{"surface":"partner","method":"PATCH","path":"/v1/partner/audience/segments/{segmentId}","operation_id":"updateAudienceSegment","summary":"Update audience segment","description":"Partially updates an audience segment. All fields are optional.\nRequires usage-based billing (`requireBilling('data_api')`).\n","agent_safe":false,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Audience Segments"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PATCH","path":"/v1/partner/audience/segments/<segmentId>"}},{"surface":"partner","method":"PATCH","path":"/v1/partner/device/{deviceId}","operation_id":"updateDevice","summary":"Update device metadata","description":"Partially update device metadata. Only provided fields are updated.\n`device_type` changes only the partner category. Advertising type,\nhardware identity, and bidding settings remain unchanged. Unsupported\ncategories return HTTP 400 with `INVALID_DEVICE_TYPE`.\nChanges propagate to the linked screen record.\nIf location changes, venue enrichment is re-triggered.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PATCH","path":"/v1/partner/device/<deviceId>"}},{"surface":"partner","method":"PATCH","path":"/v1/partner/device/{deviceId}/impression-multiplier","operation_id":"updateDeviceImpressionMultiplier","summary":"Update a device's publisher impression multiplier","description":"Update the existing screen's publisher-declared audience impressions per ad play.\nAuthenticate with the owning partner API key. The device and linked screen must\nbe active records owned by that partner; another partner's device returns 404.\nThis writes only the screen multiplier and its update timestamp, preserving its\ndevice IDs and other settings. Repeat requests are idempotent.\n\nSend a JSON number between 0.1 and 100 inclusive, matching the venue-screen\neditor's range. Values are stored to four decimal places. Use the measurement\nprovider's value for the applicable spot duration; a daily traffic total is a\ndifferent unit. Repeat this call for each device in your fleet.\n\nThe saved value is returned here and in GET /v1/partner/device/{deviceId}\nand GET /v1/partner/devices. It feeds the existing publisher-estimate inventory\nand uncurated bid paths; measured quantity and billing keep their existing precedence.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PATCH","path":"/v1/partner/device/<deviceId>/impression-multiplier"}},{"surface":"partner","method":"PATCH","path":"/v1/partner/network-config","operation_id":"patchNetworkConfig","summary":"Partial update network configuration","description":"Partial update of specific fields within the network configuration.\nOnly the provided nested keys are updated; unmentioned keys are preserved.\n\n**Accepted top-level keys:** `default_content_preferences`, `default_waterfall`, `branding`, `storefront`, `ad_delivery`\n\nSource order, timeout, polling guidance, and cache changes apply on the\nnext `/ads` request. Clients should snapshot `total_timeout_ms` and the\nordered `sources` array for the current ad break instead of changing a\nloading deadline midway through playback.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Network Config"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PATCH","path":"/v1/partner/network-config"}},{"surface":"partner","method":"PATCH","path":"/v1/partner/screens/{screenId}/content-preferences","operation_id":"patchContentPreferences","summary":"Update content preferences (partial)","description":"Partial update of content preferences. Only updates provided fields.\nTriggers real-time update to the screen via WebSocket.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PATCH","path":"/v1/partner/screens/<screenId>/content-preferences"}},{"surface":"partner","method":"PATCH","path":"/v1/partner/screens/{screenId}/display-settings","operation_id":"patchDisplaySettings","summary":"Update display settings (partial)","description":"Partial update of display settings.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PATCH","path":"/v1/partner/screens/<screenId>/display-settings"}},{"surface":"partner","method":"PATCH","path":"/v1/partner/screens/{screenId}/programmatic-settings","operation_id":"patchProgrammaticSettings","summary":"Update programmatic settings (partial)","description":"Partial update of programmatic settings.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PATCH","path":"/v1/partner/screens/<screenId>/programmatic-settings"}},{"surface":"partner","method":"PATCH","path":"/v1/partner/screens/{screenId}/sensing/settings","operation_id":"updateScreenSensingSettings","summary":"Update a screen's AI sensing performance mode","description":"Applies a high-level sensing performance mode to a screen you own. The\nserver translates the mode into the canonical sensing profile, cadence,\nprogram spec, and device command. Use this instead of editing raw model\nids, cadence, or device tier.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PATCH","path":"/v1/partner/screens/<screenId>/sensing/settings"}},{"surface":"partner","method":"PATCH","path":"/v1/partner/sensing/routes/{routeId}","operation_id":"updatePartnerSensingRoute","summary":"Update a Sense Anything Route (partial, non-clobbering)","description":"Update only the fields present in the body; omitted fields are left\nunchanged. Re-targeting `screenMongoIds` re-authorizes the new screen set\nagainst the partner's owned screens (403 on a foreign screen).\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PATCH","path":"/v1/partner/sensing/routes/<routeId>"}},{"surface":"partner","method":"PATCH","path":"/v1/partner/team/{userId}/role","operation_id":"changeTeamMemberRole","summary":"Change team member role","description":"Change the role of a team member. Cannot change owner's role.\n\n**Authorization:** Requires both partner API key AND user JWT with owner role.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Team"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PATCH","path":"/v1/partner/team/<userId>/role"}},{"surface":"partner","method":"PATCH","path":"/v1/partner/webhooks/{webhookId}","operation_id":"updateWebhook","summary":"Update webhook","description":"Update webhook URL, events, or status.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Webhooks"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PATCH","path":"/v1/partner/webhooks/<webhookId>"}},{"surface":"partner","method":"POST","path":"/v1/federated/gradients","operation_id":"federatedGradientUpload","summary":"Upload sparsified gradients for multi-slice federated aggregation.","description":"Used by the on-device `FederatedTrainer` to upload top-K-sparsified gradients every ~6 hours. The server buffers gradients per slice (modelType × venueType × venueSubtype × daypart × geo × deviceProfile) and triggers aggregation when the slice buffer reaches the configured threshold. Authentication is device-fingerprint (`X-Device-Fingerprint` header or `screenId` in the body).","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Federated Learning"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/federated/gradients"}},{"surface":"partner","method":"POST","path":"/v1/federated/performance","operation_id":"federatedPerformanceUpload","summary":"Report on-device model performance metrics.","description":"Fire-and-forget INSERT into `federated_model_performance`. Tracks accuracy / latency / anomaly counts per (device, modelType, venueType, modelVersion) so the operator can spot drift before it shows up in revenue.","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Federated Learning"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/federated/performance"}},{"surface":"partner","method":"POST","path":"/v1/federated/taste","operation_id":"federatedTasteUpload","summary":"Upload a screen-taste embedding for content personalization aggregation.","description":"Phase 3 endpoint. The device computes a fixed-length screen-taste embedding from accumulated content-impression observations and uploads it for slice aggregation. Embeddings are signed with the device Ed25519 attestation key.","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Federated Learning"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/federated/taste"}},{"surface":"partner","method":"POST","path":"/v1/partner/ads/self-promo","operation_id":"createPartnerSelfPromoAd","summary":"Create self-promo ad (URL-based)","description":null,"agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/ads/self-promo"}},{"surface":"partner","method":"POST","path":"/v1/partner/ads/self-promo/{ad_id}/disable","operation_id":"disableSelfPromoAd","summary":"Disable a self-promo ad","description":"Marks a self-promo creative as disabled. Disabled self-promos do not\nenter the waterfall and stop accruing impressions. Idempotent: a\nsecond call returns 200 with the same payload.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/ads/self-promo/<ad_id>/disable"}},{"surface":"partner","method":"POST","path":"/v1/partner/ads/self-promo/schedule","operation_id":"schedulePartnerSelfPromo","summary":"Schedule existing self-promo ad to screens","description":null,"agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/ads/self-promo/schedule"}},{"surface":"partner","method":"POST","path":"/v1/partner/ads/self-promo/upload","operation_id":"createPartnerSelfPromoUpload","summary":"Create self-promo ad (upload)","description":null,"agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/ads/self-promo/upload"}},{"surface":"partner","method":"POST","path":"/v1/partner/agent/register","operation_id":"registerAgent","summary":"Register an AI agent","description":"Self-service registration for AI agents and automated systems. Returns a\ncentralized platform API key, the exact MCP endpoint, sandbox screens for\ntesting, and free tier information. The same returned key can be supplied\nto `POST /openrtb/v2/onboard` to add buyer capabilities without minting a\nsecond credential.\n\nNo authentication required — designed for fully autonomous agent onboarding.\nRate limited to 5 registrations per hour per IP.\n\nProgrammatic buyer flow:\n```bash\ncurl -sS -X POST https://api.trillboards.com/v1/partner/agent/register \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"agent_type\":\"dsp_agent\",\"name\":\"Example buyer agent\",\"email\":\"programmatic@example.com\",\"company\":\"Example Buyer\"}'\n\ncurl -sS -X POST https://api.trillboards.com/openrtb/v2/onboard \\\n  -H 'Authorization: Bearer <api_key_from_registration>' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"company_name\":\"Example Buyer\",\"seat_id\":\"example_buyer\",\"contact_email\":\"programmatic@example.com\"}'\n```\nThe second call links the buyer seat and capabilities to the original key.\nIt does not mint a DSP key.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Agent Registration"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/agent/register"}},{"surface":"partner","method":"POST","path":"/v1/partner/api-key/rotate","operation_id":"rotateApiKey","summary":"Rotate API key","description":"Generates a new API key with a 24-hour grace period for the old key.\nBoth keys work during the grace period, after which the old key is invalidated.\n\n**Important:** The new API key is only shown once. Store it securely!\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/api-key/rotate"}},{"surface":"partner","method":"POST","path":"/v1/partner/audience/segments","operation_id":"createAudienceSegment","summary":"Create audience segment","description":"Creates a custom audience segment based on rules (demographics, venue type, geography, etc.).\nRequires usage-based billing (`requireBilling('data_api')`).\n","agent_safe":false,"idempotent":false,"cost_tier":"metered","llm_example":null,"tags":["Audience Segments"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/audience/segments"}},{"surface":"partner","method":"POST","path":"/v1/partner/audience/segments/{segmentId}/match","operation_id":"matchAudienceSegment","summary":"Match screens to segment","description":"Finds screens whose audience matches the segment rules.\nRequires usage-based billing (`requireBilling('data_api')`).\n","agent_safe":false,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Audience Segments"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/audience/segments/<segmentId>/match"}},{"surface":"partner","method":"POST","path":"/v1/partner/audience/segments/{segmentId}/reach","operation_id":"estimateSegmentReach","summary":"Estimate segment reach","description":"Estimates the potential reach of a segment including matched screens,\nprojected daily impressions, and estimated revenue.\nRequires usage-based billing (`requireBilling('data_api')`).\n","agent_safe":true,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Audience Segments"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/audience/segments/<segmentId>/reach"}},{"surface":"partner","method":"POST","path":"/v1/partner/audience/segments/{segmentId}/vast-tag","operation_id":"generateSegmentVastTag","summary":"Generate VAST tag for segment","description":"Generates a VAST tag URL targeting screens that match the segment rules.\nRequires usage-based billing (`requireBilling('data_api')`).\n","agent_safe":false,"idempotent":true,"cost_tier":"metered","llm_example":null,"tags":["Audience Segments"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/audience/segments/<segmentId>/vast-tag"}},{"surface":"partner","method":"POST","path":"/v1/partner/billing/cancel","operation_id":"cancelBillingSubscription","summary":"Cancel the partner's subscription at period end","description":"Marks the partner's subscription for cancellation at the end of the\ncurrent billing period. The partner retains access until then; no\nproration is applied. Idempotent.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/billing/cancel"}},{"surface":"partner","method":"POST","path":"/v1/partner/billing/credits","operation_id":"purchaseCredits","summary":"Purchase committed-use credits","description":"Creates or retrieves the stable credit-purchase operation and confirms its Stripe payment.\nCredits are applied only after verified successful capture. If requires_action\nis true, complete Stripe confirmation with client_secret and read the committed\nbalance after settlement; HTTP201 alone does not prove funded credit.\n\nAvailable tiers:\n| Tier | Price | Credits | Bonus credit |\n|------|-------|---------|----------|\n| tier_500 | $500 | $625 | 25% |\n| tier_2000 | $2,000 | $3,100 | 55% |\n| tier_5000 | $5,000 | $10,000 | 100% |\n\nRequires an active billing setup (payment method attached).\n","agent_safe":false,"idempotent":false,"cost_tier":"paid","llm_example":null,"tags":["Billing"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/billing/credits"}},{"surface":"partner","method":"POST","path":"/v1/partner/billing/credits/checkout","operation_id":"createCreditCheckout","summary":"Create Stripe Checkout session for credits","description":"Creates a Stripe Checkout session for purchasing committed-use credits.\nRedirect the user to the returned checkout_url to complete payment.\nCredits are automatically applied after successful payment via webhook.\n","agent_safe":false,"idempotent":false,"cost_tier":"paid","llm_example":null,"tags":["Billing"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/billing/credits/checkout"}},{"surface":"partner","method":"POST","path":"/v1/partner/billing/setup","operation_id":"setupBilling","summary":"Set up usage-based billing","description":"Saves a Stripe payment method on your existing partner billing account.\nPaid API usage requires funded credit or an explicitly approved postpay\nallowance. Saving a payment method does not add credit or fund media.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Billing"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/billing/setup"}},{"surface":"partner","method":"POST","path":"/v1/partner/billing/subscribe","operation_id":"createBillingSubscription","summary":"Subscribe the partner to a billing plan","description":"Starts or upgrades a subscription to the supplied `tier`. Returns the\nStripe checkout-session URL when a card on file is missing; otherwise\nrecords the new plan and returns the active subscription.\n","agent_safe":false,"idempotent":false,"cost_tier":"paid","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/billing/subscribe"}},{"surface":"partner","method":"POST","path":"/v1/partner/billing/webhook","operation_id":"stripeWebhookCallback","summary":"Stripe webhook callback","description":"Stripe POSTs subscription, invoice, and customer events to this\nendpoint. The request is verified via the `Stripe-Signature` header\nagainst the configured webhook secret; unsigned or invalid\nrequests are rejected with 400. NOT for partner use — listed here\nonly to keep the OpenAPI surface complete.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Webhooks"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/billing/webhook"}},{"surface":"partner","method":"POST","path":"/v1/partner/creative-experiments","operation_id":"createCreativeFeedbackExperiment","summary":"Create an experiment on a funded native media-buy placement","description":"Uses the existing native paid media-buy, generated creative, experiment and policy authorities. Partner and advertiser identity are derived from the authenticated credential.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":"Create an experiment on a funded native media-buy placement","tags":["Creative Feedback"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/creative-experiments"}},{"surface":"partner","method":"POST","path":"/v1/partner/creative-experiments/{experiment_id}/brand","operation_id":"configureCreativeFeedbackBrand","summary":"Configure your native brand profile and enable its booked reactive campaign","description":"Uses the existing native paid media-buy, generated creative, experiment and policy authorities. Partner and advertiser identity are derived from the authenticated credential.","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":"Configure your native brand profile and enable its booked reactive campaign","tags":["Creative Feedback"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/creative-experiments/<experiment_id>/brand"}},{"surface":"partner","method":"POST","path":"/v1/partner/creative-experiments/{experiment_id}/creatives/generate","operation_id":"generateCreativeFeedbackCreative","summary":"Generate a native brand creative for explicit review and moderation","description":"Uses the experiment’s native advertiser and advertisement. AI moderation and explicit brand approval remain separate native steps. One operation uses the existing data_api/api_calls tariff; native ad spend remains separate. An in-progress or committed idempotency key returns 409 OPERATION_IN_PROGRESS or OPERATION_ALREADY_COMMITTED; committed retry details retain error.help.usage_receipt.","agent_safe":false,"idempotent":true,"cost_tier":"metered","llm_example":"Generate a native brand creative for explicit review and moderation","tags":["Creative Feedback"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/creative-experiments/<experiment_id>/creatives/generate"}},{"surface":"partner","method":"POST","path":"/v1/partner/creative-experiments/{experiment_id}/creatives/import","operation_id":"importCreativeFeedbackCreative","summary":"Import an AI model’s image through native bytes, moderation and brand review","description":"Provide exactly one of asset_url, source_creative_id or image_base64. Inline bytes require content_type. PNG, JPEG and WebP use the native image staging and actual MIME/hash validation. source_creative_id is resolved within the authenticated partner. Brand approval and AI moderation remain explicit native steps. One operation uses the existing data_api/api_calls tariff.","agent_safe":false,"idempotent":true,"cost_tier":"metered","llm_example":"Import an AI model’s image through native bytes, moderation and brand review","tags":["Creative Feedback"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/creative-experiments/<experiment_id>/creatives/import"}},{"surface":"partner","method":"POST","path":"/v1/partner/creative-experiments/{experiment_id}/creatives/review","operation_id":"reviewCreativeFeedbackCreative","summary":"Approve or reject your generated creative using native brand review","description":"Uses the existing native paid media-buy, generated creative, experiment and policy authorities. Partner and advertiser identity are derived from the authenticated credential.","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":"Approve or reject your generated creative using native brand review","tags":["Creative Feedback"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/creative-experiments/<experiment_id>/creatives/review"}},{"surface":"partner","method":"POST","path":"/v1/partner/creative-experiments/{experiment_id}/stop","operation_id":"stopCreativeFeedbackExperiment","summary":"Stop the native experiment at the expected policy version","description":"Uses the existing native paid media-buy, generated creative, experiment and policy authorities. Partner and advertiser identity are derived from the authenticated credential.","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":"Stop the native experiment at the expected policy version","tags":["Creative Feedback"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/creative-experiments/<experiment_id>/stop"}},{"surface":"partner","method":"POST","path":"/v1/partner/creative-experiments/{experiment_id}/variants","operation_id":"registerCreativeFeedbackVariants","summary":"Register approved native generated creative variants","description":"Uses the existing native paid media-buy, generated creative, experiment and policy authorities. Partner and advertiser identity are derived from the authenticated credential.","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":"Register approved native generated creative variants","tags":["Creative Feedback"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/creative-experiments/<experiment_id>/variants"}},{"surface":"partner","method":"POST","path":"/v1/partner/creatives","operation_id":"submitPartnerCreative","summary":"Submit a creative for classification","description":"Point us at a public/CDN image or video URL and we return the same\nstructured classification the Trillboards venue portal shows for every\nserved ad: moderation verdict, age rating, confidence, IAB categories,\nsubjects, keywords, on-screen text (OCR), setting/mood/quality, and\nbrand/category.\n\nClassification is asynchronous (a few seconds for images, up to ~60s for\nvideo). The response carries a canonical `id` of the form `sha256:<hex>` —\nthe content hash we computed from the fetched bytes. Poll\n`GET /creatives/{ref}` with that id until `status` is terminal\n(`approved`, `flagged`, or `blocked`). If we have already classified the\nsame bytes, the full result is returned immediately with `200`.\n\nUse the returned `id` (or `content_sha256`) as `creative_ref` when you\npost log-level plays, so your plays join to this classification.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Creative Moderation"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/creatives"}},{"surface":"partner","method":"POST","path":"/v1/partner/device","operation_id":"registerDevice","summary":"Register a device","description":"Register a new device or update an existing one.\n\nIf a device with the same `device_id` already exists, it will be updated.\n`device_type` is the partner's mutable category. Supplying it updates the\ncategory; omitting it preserves an existing category and defaults a new\ndevice to `other`. Category updates preserve advertising classification.\n\nThe response includes an `embed_url` that should be loaded in your device's WebView.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/device"}},{"surface":"partner","method":"POST","path":"/v1/partner/device/{deviceId}/commands/{commandId}/ack","operation_id":"ackDeviceCommand","summary":"Acknowledge a device command","description":"Acknowledges receipt and execution of a command sent to a device.\nTypically called by the SDK/device after processing a fleet command.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/device/<deviceId>/commands/<commandId>/ack"}},{"surface":"partner","method":"POST","path":"/v1/partner/device/{deviceId}/heartbeat","operation_id":"heartbeat","summary":"Device heartbeat","description":"Mark a device online and record `lastHeartbeatAt`.\n\nThe embeddable agent-core Sensing SDK and CTV Measurement SDK send this\nautomatically on their configured heartbeat cadence. Custom players\nmay POST the same payload themselves.\n`:deviceId` accepts your `external_id` (the same value you registered)\nor the device fingerprint. Read the result back from\n`GET /device/{deviceId}/heartbeat` — `lastHeartbeatAt` is NOT included\non the plain `GET /device/{deviceId}` response.\n\nSupports structured telemetry payloads (`telemetry`, `sdk`, `device`, `network`, `ad`)\nand legacy flat heartbeat keys for backward compatibility.\n\nThe request body accepts legacy flat/nested telemetry\n(`PartnerHeartbeatRequest`), agent-core sensing fields, or both together.\nThe sensing schema (`PartnerDeviceHeartbeatBody`) describes which\nsensing fields actually ship in production (BLE, WiFi, mDNS, SSDP,\nHTTP probes, skip-reason counts) and explicitly excludes stub fields\nwith 0% production population (Phase 6 UWB / Auracast / Channel\nSounding, MDM enrollment, native sensors, CSI).\n\nThis registered-device heartbeat is intentionally unauthenticated.\nLiveness, `advertising_identity`, and `extended_identities` use the same\ndevice binding: `deviceId` must resolve to a device that was registered\nalready, and every identity envelope must pass the typed validation\nbelow. No partner API key or device JWT is required.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/device/<deviceId>/heartbeat"}},{"surface":"partner","method":"POST","path":"/v1/partner/device/{deviceId}/interactions","operation_id":"recordPartnerDeviceInteraction","summary":"Report a rider interaction from the partner SDK (reportInteraction)","description":"The partner SDK's `reportInteraction` host hook: what a rider did with this\ndevice's screen — a tap, a QR code shown or scanned, an attention prompt — with\nthe creative on screen and how far into it. Stored for measurement only; never\nbilled, and never an ad-lifecycle event (those go to\n`POST /v1/partner/device/{deviceId}/programmatic-event`).\n\nAuthenticated with the device token the SDK holds. The screen is this device's\nown binding and the partner is that screen's partner; the body may not name a\nscreen.\n\nIdempotent per device on `interaction_id`: a retried interaction is answered\n`duplicate` and stored once, and two devices that use the same id report two\ninteractions.\n\n`occurred_at` is the device clock and must lie within the last 7 days and at most\n5 minutes ahead of the server clock; any other time is refused with 400.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["SDK Host Hooks"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/device/<deviceId>/interactions"}},{"surface":"partner","method":"POST","path":"/v1/partner/device/{deviceId}/plays","operation_id":"recordPartnerDevicePlay","summary":"Report a play from the partner SDK (reportPlay)","description":"The partner SDK's `reportPlay` host hook: one log-level play of an ad the host\napp played on this device's screen, into the same non-billing store as\n`POST /v1/partner/plays` — the same body without `screen_id`, plus the\nTrillboards `impression_id` when the host played an ad Trillboards served.\n\nAuthenticated with the device token the SDK holds, never a partner API key.\nThe screen is this device's own binding and the partner is that screen's\npartner, so the body may not name a screen: a body carrying `screen_id`, or any\nfield not listed, is refused with 400.\n\nIdempotent on `play_id`, shared with `POST /v1/partner/plays`, so a play\nreported through both is one play. When `impression_id` matches a\nTrillboards-served play on the same screen, on the same or an adjacent UTC day,\nor, without one, the same `ad_id` completes within 5 s of one, the Trillboards\nrecord of that play is kept and this report is not counted a second time.\nMeasurement plays are never billed.\n\n`completed_at` is the device clock and must lie within the last 7 days and at\nmost 5 minutes ahead of the server clock; any other time is refused with 400.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["SDK Host Hooks","Measurement Plays"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/device/<deviceId>/plays"}},{"surface":"partner","method":"POST","path":"/v1/partner/device/{deviceId}/programmatic-event","operation_id":"recordProgrammaticEvent","summary":"Record programmatic ad event (Server-side Analytics)","description":"**SDK Analytics Endpoint** - Records programmatic ad lifecycle events for\nTrillboards' internal analytics and triggers partner webhooks when subscribed.\n\n**Important**: This endpoint is for **server-side event recording only**.\nIt does NOT push events to your native Android, iOS, or CTV applications.\n\nFor receiving real-time events in native applications, use the\n**JavaScript Native Bridge Protocol** instead. The SDK automatically sends\nevents to standard native interfaces (Android WebView, iOS WKWebView,\nReact Native, Flutter, etc.).\n\nSee documentation: https://api.trillboards.com/docs/integrations/partner-native-bridge.md\n\n### Events Recorded:\n- `ad_started` - Ad began playing\n- `ad_ended` - Ad finished (complete or skipped)\n- `no_fill` - No programmatic ad available\n- `error` - Ad playback error\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Ads"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/device/<deviceId>/programmatic-event"}},{"surface":"partner","method":"POST","path":"/v1/partner/device/{deviceId}/trips","operation_id":"recordPartnerDeviceTripState","summary":"Report a taxi-meter state change from the partner SDK (setTripState)","description":"The partner SDK's `setTripState` host hook: one meter state change — meter on or\noff, fare, tip, payment type, riders, destination — on this device's screen.\nStored for measurement only; never billed.\n\nAuthenticated with the device token the SDK holds. The screen is this device's\nown binding and the partner is that screen's partner; the body may not name a\nscreen.\n\nIdempotent per device on (`trip_id`, `event_seq`): `event_seq` increases within a\ntrip and a retry re-sends the same value, so a retried event is answered\n`duplicate` and stored once. Hosts number trips per vehicle, so two devices that\nboth report trip `1` report two trips.\n\n`state_at` is the device clock and must lie within the last 7 days and at most\n5 minutes ahead of the server clock; any other time is refused with 400.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["SDK Host Hooks"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/device/<deviceId>/trips"}},{"surface":"partner","method":"POST","path":"/v1/partner/devices","operation_id":"registerDeviceV2","summary":"Register a device (V2 single-call onboarding)","description":"Register — or idempotently re-register, keyed on `external_id` — a single partner device and provision its screen in one call. Returns the embeddable player URL and the server-derived device identity. Always send the device's real WebView `ua`: it is the single most important field for DSP biddability.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/devices"}},{"surface":"partner","method":"POST","path":"/v1/partner/devices/batch","operation_id":"registerDevicesBatch","summary":"Bulk register devices","description":"Register up to 100 devices in a single request.\n\nEach device must include `device_id`. Optional `defaults` are applied to every device.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/devices/batch"}},{"surface":"partner","method":"POST","path":"/v1/partner/devices/batch/orientation","operation_id":"batchUpdateDeviceOrientation","summary":"Batch update screen orientation","description":"Update `screen_orientation` for multiple devices/screens in one request.\nAccepts device identifiers (ObjectId, fingerprint, or partner_external_id).\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/devices/batch/orientation"}},{"surface":"partner","method":"POST","path":"/v1/partner/devices/reconcile","operation_id":"reconcileDevices","summary":"Reconcile device fleet","description":"Read-only diff. Post the `external_ids` you believe you have registered\nand get back how they map to the devices Trillboards actually has for\nyou: `matched`, `missing` (you listed, not registered), `orphan`\n(registered, you did not list), `malformed` (registered with an invalid\nexternal id), and `duplicate` (one external id on more than one device),\nplus a `summary`. Self-service replacement for hand-rolled fleet-sync\ntooling. No writes.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Devices"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/devices/reconcile"}},{"surface":"partner","method":"POST","path":"/v1/partner/earnings/payout","operation_id":"requestPayout","summary":"Request a payout","description":"Request a payout of available earnings to your connected Stripe account.\nRequires Stripe Connect onboarding to be complete.\n\n**Requirements:**\n- Stripe account must be connected and active\n- Available balance must meet minimum threshold ($100 default)\n- If `amount_cents` not specified, full available balance is requested\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Earnings"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/earnings/payout"}},{"surface":"partner","method":"POST","path":"/v1/partner/fleet/command","operation_id":"pushFleetCommand","summary":"Push command to fleet","description":"Sends a command to all devices in the fleet (or a filtered subset).\nCommands expire after 10 minutes. Maximum 500 devices per batch.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Fleet Management"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/fleet/command"}},{"surface":"partner","method":"POST","path":"/v1/partner/fleet/settings","operation_id":"updateFleetSettings","summary":"Update fleet-wide settings","description":"Updates settings across all devices in the fleet (or a filtered subset).\nSettings are merged with existing values, not replaced.\n\n**Allowed settings:** `header_bidding_settings`, `content_preferences`, `display_settings`\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Fleet Management"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/fleet/settings"}},{"surface":"partner","method":"POST","path":"/v1/partner/impression","operation_id":"recordImpressionPost","summary":"Record impression (POST)","description":null,"agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Impressions"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/impression"}},{"surface":"partner","method":"POST","path":"/v1/partner/impressions/batch","operation_id":"recordImpressionsBatch","summary":"Batch record impressions","description":"Record multiple impressions at once. Useful for syncing offline impressions.\n\nMaximum 100 impressions per request.\nSigned impressions can include `sig` and `sig_ts` per impression (see `/impression` signature format).\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Impressions"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/impressions/batch"}},{"surface":"partner","method":"POST","path":"/v1/partner/plays","operation_id":"recordPartnerPlay","summary":"Record a log-level play","description":"Record a single log-level play for an ad you serve yourself (not through\nour programmatic pipes). The play lands in a dedicated NON-billing store\nand joins to (a) the creative classification you submitted via\nPOST /creatives — by `creative_ref` (content_sha256) — and (b) our\naudience moments captured on the screen during the play window. It then\nsurfaces through GET /analytics/content-performance and\n/analytics/creative-scenes for your screens.\n\nIdempotent on `play_id`. `screen_id` must be one of your registered\nearner screens. Measurement plays are never billed.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Measurement Plays"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/plays"}},{"surface":"partner","method":"POST","path":"/v1/partner/plays/batch","operation_id":"recordPartnerPlaysBatch","summary":"Record a batch of log-level plays","description":"Submit up to 500 plays in one request (each the same shape as POST /plays).\nEvery play is validated independently; the response reports per-play\nrejections (invalid_creative_ref, screen_not_owned).\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Measurement Plays"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/plays/batch"}},{"surface":"partner","method":"POST","path":"/v1/partner/programmatic-event","operation_id":"recordProgrammaticEventLegacyAlias","summary":"Record programmatic ad event (legacy SDK alias)","description":"Backward-compatible alias for SDKs that send the device identifier in\nthe request body instead of the `/device/{deviceId}/programmatic-event`\npath. New integrations should use the device-scoped endpoint. Runtime\nstill accepts legacy `deviceId` bodies, but the documented schema uses\nsnake_case to keep generated SDK models unambiguous.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Ads"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/programmatic-event"}},{"surface":"partner","method":"POST","path":"/v1/partner/quick-start","operation_id":"quickStart","summary":"Quick start — register partner and first device in one call","description":"Registers a new partner and creates their first device in a single API call.\nReturns API key, device credentials, SDK snippets, and configuration.\n\n**Rate limited:** 5 requests per hour per IP address.\n\n**Important:** The API key is only shown once in the response. Store it securely!\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/quick-start"}},{"surface":"partner","method":"POST","path":"/v1/partner/register","operation_id":"registerPartner","summary":"Register a new partner","description":"Register once to receive a Partner API key.\nPartners are auto-activated for instant onboarding.\n\n**Important:** The API key is only shown once in the response. Store it securely!\n\nThe default flow is intentionally one-stop: send your organization name,\nslug, and contact email. Trillboards returns a Partner API key that can\nauthenticate partner account, device, ad-serving, impression, analytics,\nCMS, webhook, dashboard, earnings, and team-management endpoints in this\nspec.\n\n`partner_type` is optional and kept for backward compatibility with older\nintegrations. Most developers should omit it — it never forks the key\nyou get back. Every partner registers once and receives the same\nPartner API key, which already carries `sdk:*` for the CTV Measurement\ningestion surface and `mcp:tools` for capability-authorized AI tools.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Partners"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/register"}},{"surface":"partner","method":"POST","path":"/v1/partner/sandbox/event","operation_id":"generateSandboxEvent","summary":"Generate a test event","description":"Generates a realistic test event that can be used to validate webhook integrations.\nIf an SSE connection is active, the event is also delivered via SSE.\n\n**Supported event types:** `device.online`, `device.offline`, `impression.recorded`,\n`impression.verified`, `campaign.allocated`, `payout.processed`, `sensing.threshold_crossed`, `audience.spike`,\n`audience.demographics_update`, `audience.purchase_intent`, `audience.venue_busy`,\n`audience.context_update`, `venue.traffic_summary`, `auction.completed`, `bid.won`,\n`bid.lost`, `creative.performance`, `screen.anomaly_detected`\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Sandbox"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/sandbox/event"}},{"surface":"partner","method":"POST","path":"/v1/partner/sandbox/reset","operation_id":"resetSandbox","summary":"Reset sandbox data","description":"Clears all sandbox screens and events for the partner.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Sandbox"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/sandbox/reset"}},{"surface":"partner","method":"POST","path":"/v1/partner/sandbox/screen","operation_id":"createSandboxScreen","summary":"Create a sandbox screen","description":"Creates a synthetic test screen with realistic data for integration testing.\nMaximum 10 sandbox screens per partner. Sandbox data expires after 24 hours.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Sandbox"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/sandbox/screen"}},{"surface":"partner","method":"POST","path":"/v1/partner/screen-groups","operation_id":"createScreenGroup","summary":"Create a screen group","description":"Create a reusable, named, partner-scoped set of screens. Optionally seed\nit with `screen_ids` (each must be a screen this partner owns). Gated by\nSCREEN_GROUPS_ENABLED.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screen-groups"}},{"surface":"partner","method":"POST","path":"/v1/partner/screen-groups/{groupId}","operation_id":"updateScreenGroup","summary":"Update a screen group","description":"Partial, non-clobbering update of name / description.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screen-groups/<groupId>"}},{"surface":"partner","method":"POST","path":"/v1/partner/screen-groups/{groupId}/commands","operation_id":"createScreenGroupCommand","summary":"Run a fleet command against a screen group","description":"Fans a device command out across the group's member screens and returns a pollable\nfleet-command job (poll GET /fleet-commands/{id}). Max 500 member screens per command.\nGated by SCREEN_GROUPS_ENABLED.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screen-groups/<groupId>/commands"}},{"surface":"partner","method":"POST","path":"/v1/partner/screen-groups/{groupId}/members","operation_id":"addScreenGroupMembers","summary":"Add members to a screen group","description":"Idempotent. Partial-success: screens not owned by this partner are\nreturned in `rejected` rather than failing the whole request.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screen-groups/<groupId>/members"}},{"surface":"partner","method":"POST","path":"/v1/partner/screens/{screenId}/apply-preset","operation_id":"applyContentPreset","summary":"Apply content preset to screen","description":"Apply a predefined content preset to a screen.\nThis overwrites existing content preferences with the preset values.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screens/<screenId>/apply-preset"}},{"surface":"partner","method":"POST","path":"/v1/partner/screens/{screenId}/default-streams","operation_id":"createPartnerDefaultStream","summary":"Create a default stream (URL-based)","description":null,"agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screens/<screenId>/default-streams"}},{"surface":"partner","method":"POST","path":"/v1/partner/screens/{screenId}/default-streams/images","operation_id":"createPartnerDefaultStreamImages","summary":"Upload image carousel default stream","description":null,"agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screens/<screenId>/default-streams/images"}},{"surface":"partner","method":"POST","path":"/v1/partner/screens/{screenId}/default-streams/upload","operation_id":"createPartnerDefaultStreamUpload","summary":"Upload a video default stream","description":null,"agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screens/<screenId>/default-streams/upload"}},{"surface":"partner","method":"POST","path":"/v1/partner/screens/{screenId}/sensing/interpret-intent","operation_id":"interpretScreenSensingIntent","summary":"Generate a custom sensing profile from a natural-language intent","description":"\"Sense Anything\" for partners. Given a natural-language intent, an LLM\ngenerates a custom sensing profile — a schema of custom fields the screen\nwill sense. This only GENERATES the profile (it is not persisted); POST the\nreturned profile to /screens/{screenId}/sensing/profile to deploy it.\nScoped to a screen the authenticated partner owns.\n","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screens/<screenId>/sensing/interpret-intent"}},{"surface":"partner","method":"POST","path":"/v1/partner/screens/{screenId}/sensing/profile","operation_id":"deployScreenSensingProfile","summary":"Deploy a custom sensing profile to a screen","description":"Persist + activate a custom sensing profile (from interpret-intent) on a\nscreen you own. Once active, the audience-analyze pipeline emits the\nprofile's custom chips for this screen's observations. Scoped to a screen\nthe authenticated partner owns.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screens/<screenId>/sensing/profile"}},{"surface":"partner","method":"POST","path":"/v1/partner/screens/batch/content-preferences","operation_id":"batchUpdateContentPreferences","summary":"Batch update content preferences","description":"Update content preferences for multiple screens at once.\nCan apply custom preferences or a preset to up to 100 screens.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screens/batch/content-preferences"}},{"surface":"partner","method":"POST","path":"/v1/partner/screens/batch/display-settings","operation_id":"batchUpdateDisplaySettings","summary":"Batch update display settings","description":"Update display settings for multiple screens (up to 100).","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screens/batch/display-settings"}},{"surface":"partner","method":"POST","path":"/v1/partner/screens/batch/programmatic-settings","operation_id":"batchUpdateProgrammaticSettings","summary":"Batch update programmatic settings","description":"Update programmatic settings for multiple screens (up to 100).","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/screens/batch/programmatic-settings"}},{"surface":"partner","method":"POST","path":"/v1/partner/sensing/routes","operation_id":"createPartnerSensingRoute","summary":"Create a Sense Anything Route (threshold or scheduled-digest alert)","description":"Create a Route: when a sensed field crosses a threshold on a set of your\nscreens (threshold mode), or on a fixed schedule (digest mode), fire one or\nmore channel actions (webhook / managed WhatsApp / SMS / Slack / email).\nEvery screen in `screenMongoIds` must be a screen this partner owns — a\nforeign screen returns 403. Anti-noise (sustain/debounce) and an active\nwindow are first-class. Gated by the platform `SENSING_ROUTES_ENABLED`\nflag (404 when the feature is disabled).\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Audience Intelligence"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/sensing/routes"}},{"surface":"partner","method":"POST","path":"/v1/partner/stripe/connect","operation_id":"createStripeConnectAccount","summary":"Create Stripe Connect account","description":"Creates a Stripe Connect Express account for the partner. If the partner already\nhas a Stripe account, returns the existing account details with HTTP 200.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Stripe Connect"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/stripe/connect"}},{"surface":"partner","method":"POST","path":"/v1/partner/stripe/payout","operation_id":"requestStripePayout","summary":"Request a payout","description":"Initiates a payout transfer to the partner's Stripe Connect account.\nMinimum payout is $5.00 (500 cents). The partner must have an active\nStripe account and sufficient available balance.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Stripe Connect"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/stripe/payout"}},{"surface":"partner","method":"POST","path":"/v1/partner/stripe/webhook","operation_id":"handleStripeWebhook","summary":"Stripe webhook receiver","description":"Receives webhook events from Stripe for Connect account updates and transfer status changes.\nThis endpoint is called by Stripe, not by the partner. It uses Stripe signature verification.\n\n**Handled events:** `account.updated`, `transfer.created`, `transfer.paid`, `transfer.reversed`\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Stripe Connect"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/stripe/webhook"}},{"surface":"partner","method":"POST","path":"/v1/partner/team/{membershipId}/resend","operation_id":"resendInvitation","summary":"Resend invitation","description":"Resend the invitation email to a pending team member.\nGenerates a new invitation token with extended expiry.\n\n**Authorization:** Requires both partner API key AND user JWT with owner role.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Team"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/team/<membershipId>/resend"}},{"surface":"partner","method":"POST","path":"/v1/partner/team/accept","operation_id":"acceptInvitation","summary":"Accept team invitation","description":"Accept a team invitation using the token from the invitation email.\nCan be called with or without authentication.\n\n**Without auth:** User with the invited email must exist\n**With auth:** Links invitation to the authenticated user's account\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Team"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/team/accept"}},{"surface":"partner","method":"POST","path":"/v1/partner/team/invite","operation_id":"inviteTeamMember","summary":"Invite a team member","description":"Invite a new team member to join your partner organization.\nThe invitee will receive an email with instructions to join.\n\n**Authorization:** Requires both a Partner API key and a portal user JWT\nfor an owner/admin in `X-User-Token`. API keys alone cannot invite users;\nteam management is role-based so access changes are tied to a signed-in\nhuman owner or admin. Portal users can do the same action in\n**Settings → Team** after claiming the owner account created during\nregistration.\n\n**Roles:**\n- `admin` - Can manage screens, view analytics, configure webhooks\n- `viewer` - Can only view screen list and status\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Team"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/team/invite"}},{"surface":"partner","method":"POST","path":"/v1/partner/tracking/batch","operation_id":"recordTrackingBatch","summary":"Record batch impressions with proof generation","description":"High-volume endpoint for reporting multiple impressions in a single request.\n\n**Features:**\n- Process up to 100 impressions per batch\n- Ed25519 cryptographic proof for each impression\n- Duplicate detection (impressions with same impression_id + ad_id are ignored)\n- Optional audience metrics (face count, attention)\n\n**Batching Strategy:**\n- Buffer impressions client-side\n- Flush every 1-5 minutes or when buffer reaches 100\n- Handle failures with local retry queue\n\n**Proof Verification:**\nEach proof can be independently verified using the public key from `/tracking/.well-known/public-key`.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Batch Tracking"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/tracking/batch"}},{"surface":"partner","method":"POST","path":"/v1/partner/webhooks","operation_id":"createWebhook","summary":"Create webhook subscription","description":"Subscribe to webhook events. Webhooks deliver real-time notifications\nwhen events occur on your devices.\n\n**Available Events:**\n- `device.online` - Device comes online after being offline\n- `device.offline` - Device goes offline (no heartbeat for 5+ minutes)\n- `device.requirements_updated` - A screen's `monetization_status` changed (e.g. → ready when classification completes, or → blocked when it permanently fails). Payload carries the full `monetization_status` + `requirements` + `location_capability` so you can react without polling.\n- `impression.recorded` - Ad impression recorded on a device\n- `creative.rewards_ready` - Native measured play states changed for your funded creative experiment. The hint carries a reader URL and date scope; fetch the canonical reward endpoint with your own idempotency key.\n- `campaign.allocated` - New campaign allocated to a device\n- `payout.processed` - Revenue payout processed\n- `sensing.threshold_crossed` - A Sense Anything Route's threshold was crossed on one of your screens (custom sensed field)\n- `audience.spike` - A Route on a viewer-count signal crossed its threshold\n- `audience.venue_busy` - A Route on an occupancy signal crossed its threshold\n- `audience.purchase_intent` - A Route on a purchase-intent signal crossed its threshold\n- `audience.demographics_update` - A Route on a demographics signal crossed its threshold\n- `venue.traffic_summary` - A scheduled-digest Route's window elapsed (periodic traffic summary)\n- `programmatic.ad_started` - Programmatic ad started\n- `programmatic.ad_ended` - Programmatic ad ended\n- `programmatic.no_fill` - Programmatic no-fill response\n- `programmatic.error` - Programmatic error\n- `screen_group.created` - A Screen Group was created\n- `screen_group.updated` - A Screen Group's name/description was updated\n- `screen_group.deleted` - A Screen Group was deleted\n- `screen_group.member.added` - One or more screens were added to a Screen Group\n- `screen_group.member.removed` - One or more screens were removed from a Screen Group\n- `screen_group.content_policy.updated` - A Screen Group's content policy was changed\n- `screen_group.sensing_config.updated` - A Screen Group's sensing config was changed\n- `fleet_command.completed` - A group fleet command job completed\n- `fleet_command.failed` - A group fleet command job terminated with an error\n\n**Security:**\n- HTTPS required in production\n- HMAC-SHA256 signature verification\n- Secret shown only once in response - store it securely!\n\n**Signature Verification:**\n\nEvery webhook delivery carries a Stripe-style compound signature header:\n\n```\nX-Trillboards-Signature: t=<unix_seconds>,v0=<hex>,v1=<hex>\nX-Trillboards-Timestamp: <unix_seconds>\n```\n\nWhere:\n- `v1 = HMAC-SHA256(webhook_secret, \"${t}.${payload}\")` — canonical, replay-protected\n- `v0 = HMAC-SHA256(webhook_secret, payload)` — legacy body-only digest emitted\n  during the migration window for partners on pre-0.3.0 verifiers.\n\nRecommended: use `@trillboards/api-client@>=0.3.0`:\n\n```js\nimport { Webhooks } from '@trillboards/api-client';\nconst event = Webhooks.constructEvent(req.body, req.headers, process.env.WEBHOOK_SECRET);\n```\n\nFull contract + replay-window guidance: https://api.trillboards.com/docs/integrations/webhook-signature-verification.md\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Webhooks"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/webhooks"}},{"surface":"partner","method":"POST","path":"/v1/partner/webhooks/{webhookId}/test","operation_id":"testWebhook","summary":"Test webhook","description":"Send a test event to verify your webhook endpoint is working.\nThe test event uses the `test` event type with a sample payload.\n","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["Webhooks"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/partner/webhooks/<webhookId>/test"}},{"surface":"partner","method":"POST","path":"/v1/playground/{operation_id}","operation_id":"playgroundDryRun","summary":"Dry-run validate a request body against an operation schema","description":"Validates the request body against the Zod schema registered for the given operationId WITHOUT executing the actual endpoint. Returns validation results and operation metadata. Useful for AI agents and developers to pre-validate requests before sending them.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":"Before calling POST /v2/sdk/enrich, validate your payload: POST /v1/playground/sdkEnrich with the body you intend to send.","tags":["API Meta"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v1/playground/<operation_id>"}},{"surface":"partner","method":"POST","path":"/v2/sdk/enrich","operation_id":"sdkEnrich","summary":"Census + demographic enrichment from the request IP.","description":"Resolves the client IP into IAB income/age segments, education tier, connection type, and a stable HMAC household identifier. The `sdk:enrich` scope is required.","agent_safe":true,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CTV SDK & CSI"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v2/sdk/enrich"}},{"surface":"partner","method":"POST","path":"/v2/sdk/impression","operation_id":"sdkImpression","summary":"Ingest an enriched CTV/DOOH impression.","description":"Writes a sdk_impressions row with GIVT dedup, frequency counting, and reach cache updates. The `sdk:impression` scope is required.","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["CTV SDK & CSI"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v2/sdk/impression"}},{"surface":"partner","method":"POST","path":"/v2/sdk/qr/beacon","operation_id":"sdkQrBeacon","summary":"Process beacon signals associated with a prior QR scan.","description":"Attaches device fingerprinting signals (canvas hash, WebGL renderer, network info, etc.) to an already-recorded scan. The `sdk:qr` scope is required.","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["CTV SDK & CSI"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v2/sdk/qr/beacon"}},{"surface":"partner","method":"POST","path":"/v2/sdk/qr/scan","operation_id":"sdkQrScan","summary":"Record a QR-code scan for physical-to-digital attribution.","description":"Writes a qr_scan_events row tying a scan back to a specific screen, ad, and placement. The `sdk:qr` scope is required.","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":null,"tags":["CTV SDK & CSI"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v2/sdk/qr/scan"}},{"surface":"partner","method":"POST","path":"/v2/sensing/paired-training-samples","operation_id":"sensingPairedTrainingUpload","summary":"Upload a batch of paired camera/CSI training samples.","description":"Used by the Android `CsiPairedDataCollector` to upload ~500 paired samples per call. Authentication is device-token (`X-Device-Token`). Idempotent on `sampleId` — duplicate samples in-batch and across batches are silently dropped.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Sensing"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"POST","path":"/v2/sensing/paired-training-samples"}},{"surface":"partner","method":"PUT","path":"/v1/partner/creative-experiments/{experiment_id}/policy","operation_id":"updateCreativeFeedbackPolicy","summary":"Replace native creative weights at the expected policy version","description":"Uses the existing native paid media-buy, generated creative, experiment and policy authorities. Partner and advertiser identity are derived from the authenticated credential.","agent_safe":false,"idempotent":false,"cost_tier":"free","llm_example":"Replace native creative weights at the expected policy version","tags":["Creative Feedback"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PUT","path":"/v1/partner/creative-experiments/<experiment_id>/policy"}},{"surface":"partner","method":"PUT","path":"/v1/partner/network-config","operation_id":"updateNetworkConfig","summary":"Update network configuration","description":"Full update of network configuration. Each provided section is merged\nwith the existing configuration (not replaced entirely).\n\n`default_waterfall.source_order` accepts only opaque source names copied\nfrom an `/ads` response. Unlisted sources remain enabled and are appended\nin their existing order. CPM sorting is intentionally unsupported because\nconfigured CPM is not realized yield. An explicit per-screen waterfall\noverride takes precedence over this partner default.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Network Config"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PUT","path":"/v1/partner/network-config"}},{"surface":"partner","method":"PUT","path":"/v1/partner/screen-groups/{groupId}/content-policy","operation_id":"updateScreenGroupContentPolicy","summary":"Set a screen group's content policy","description":"Non-clobbering upsert — only the fields present in the body are written.\nRow existence = \"this group carries a content policy\". Busts the affected\nmember caches. Gated by SCREEN_GROUPS_ENABLED.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PUT","path":"/v1/partner/screen-groups/<groupId>/content-policy"}},{"surface":"partner","method":"PUT","path":"/v1/partner/screen-groups/{groupId}/sensing-config","operation_id":"updateScreenGroupSensingConfig","summary":"Set a screen group's sensing config","description":"Non-clobbering upsert. `profile_name` must resolve to a builtin or an\nexisting custom profile (else 400). Busts the affected member caches.\nGated by SCREEN_GROUPS_ENABLED.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["Screen Groups"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PUT","path":"/v1/partner/screen-groups/<groupId>/sensing-config"}},{"surface":"partner","method":"PUT","path":"/v1/partner/screens/{screenId}/content-preferences","operation_id":"updateContentPreferences","summary":"Update content preferences (full)","description":"Full update of content filtering preferences for a screen.\nReplaces all existing preferences with the provided values.\nTriggers real-time update to the screen via WebSocket.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PUT","path":"/v1/partner/screens/<screenId>/content-preferences"}},{"surface":"partner","method":"PUT","path":"/v1/partner/screens/{screenId}/default-streams/{streamId}","operation_id":"updatePartnerDefaultStream","summary":"Update a default stream","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PUT","path":"/v1/partner/screens/<screenId>/default-streams/<streamId>"}},{"surface":"partner","method":"PUT","path":"/v1/partner/screens/{screenId}/default-streams/order","operation_id":"reorderPartnerDefaultStreams","summary":"Reorder default streams","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PUT","path":"/v1/partner/screens/<screenId>/default-streams/order"}},{"surface":"partner","method":"PUT","path":"/v1/partner/screens/{screenId}/display-settings","operation_id":"updateDisplaySettings","summary":"Update display settings (full)","description":"Full update of display settings for a screen.\nTriggers real-time update and screen refresh via WebSocket.\n","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PUT","path":"/v1/partner/screens/<screenId>/display-settings"}},{"surface":"partner","method":"PUT","path":"/v1/partner/screens/{screenId}/l-bar","operation_id":"updatePartnerLBarSettings","summary":"Update L-Bar settings","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PUT","path":"/v1/partner/screens/<screenId>/l-bar"}},{"surface":"partner","method":"PUT","path":"/v1/partner/screens/{screenId}/l-bar/live-updates","operation_id":"updatePartnerLBarLiveUpdates","summary":"Update L-Bar live updates","description":null,"agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PUT","path":"/v1/partner/screens/<screenId>/l-bar/live-updates"}},{"surface":"partner","method":"PUT","path":"/v1/partner/screens/{screenId}/programmatic-settings","operation_id":"updateProgrammaticSettings","summary":"Update programmatic settings (full)","description":"Full update of programmatic settings.","agent_safe":false,"idempotent":true,"cost_tier":"free","llm_example":null,"tags":["CMS"],"doc_url":"https://api.trillboards.com/docs#partner","example_request":{"method":"PUT","path":"/v1/partner/screens/<screenId>/programmatic-settings"}}]}