@trillboards/api-client 3.2.16 → 3.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-G4JZA3I4.mjs → chunk-WPRB3GQB.mjs} +2 -2
- package/dist/chunk-WPRB3GQB.mjs.map +1 -0
- package/dist/generated.d.mts +7 -5
- package/dist/generated.d.ts +7 -5
- package/dist/generated.js.map +1 -1
- package/dist/generated.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-G4JZA3I4.mjs.map +0 -1
package/dist/generated.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generated/index.ts"],"names":[],"mappings":";;;AAqnVO,IAAM,cAAA,GAAiB;AAAA,EAC5B,WAAA,EAAa,GAAA;AAAA,EACb,cAAA,EAAgB,GAAA;AAAA,EAChB,OAAA,EAAS,CAAC,IAAA,EAAK,YAAA,EAAa,uBAAsB,eAAA,EAAgB,2BAAA,EAA4B,+BAA8B,cAAA,EAAe,wBAAA,EAAyB,8BAA6B,yBAAA,EAA0B,iBAAA,EAAkB,2BAA0B,aAAA,EAAc,aAAA,EAAc,qBAAoB,gBAAA,EAAiB,gBAAA,EAAiB,qBAAoB,sBAAA,EAAuB,oBAAA,EAAqB,2BAA0B,4BAAA,EAA6B,eAAA,EAAgB,oCAAmC,wBAAA,EAAyB,2BAAA,EAA4B,qBAAoB,0BAAA,EAA2B,oCAAA,EAAqC,+BAA8B,2BAAA,EAA4B,eAAA,EAAgB,qBAAoB,oCAAA,EAAqC,6BAAA,EAA8B,6BAA4B,sBAAA,EAAuB,0BAAA,EAA2B,uBAAsB,sBAAA,EAAuB,gCAAA,EAAiC,+BAAA,EAAgC,6BAAA,EAA8B,gCAAA,EAAiC,uBAAA,EAAwB,0BAAyB,uBAAA,EAAwB,2BAAA,EAA4B,kBAAiB,sBAAA,EAAuB,QAAA,EAAS,qBAAoB,yBAAA,EAA0B,eAAA,EAAgB,wBAAuB,yBAAA,EAA0B,eAAA,EAAgB,6BAA4B,cAAA,EAAe,kBAAA,EAAmB,mBAAkB,OAAA,EAAQ,WAAA,EAAY,qBAAoB,YAAA,EAAa,eAAA,EAAgB,eAAA,EAAgB,mBAAA,EAAoB,WAAA,EAAY,kBAAA,EAAmB,iBAAgB,sBAAA,EAAuB,uBAAA,EAAwB,yBAAwB,wBAAA,EAAyB,oBAAA,EAAqB,4BAA2B,kCAAA,EAAmC,4BAAA,EAA6B,2BAA0B,0BAAA,EAA2B,2BAAA,EAA4B,yBAAwB,uBAAA,EAAwB,2BAAA,EAA4B,mCAAkC,uBAAA,EAAwB,wBAAA,EAAyB,uBAAsB,mBAAA,EAAoB,iBAAA,EAAkB,yBAAA,EAA0B,qBAAA,EAAsB,iBAAA,EAAkB,cAAA,EAAe,qBAAoB,uBAAA,EAAwB,iBAAA,EAAkB,eAAc,YAAA,EAAa,kBAAA,EAAmB,eAAc,2BAAA,EAA4B,6BAAA,EAA8B,mCAAkC,yBAAA,EAA0B,uBAAA,EAAwB,0BAAyB,wBAAA,EAAyB,yBAAA,EAA0B,6BAA4B,mBAAA,EAAoB,wBAAA,EAAyB,8BAA6B,YAAA,EAAa,SAAA,EAAU,0BAAA,EAA2B,yBAAA,EAA0B,sBAAA,EAAuB,4BAAA,EAA6B,8BAA6B,qCAAA,EAAsC,4BAAA,EAA6B,qBAAoB,gCAAA,EAAiC,yBAAA,EAA0B,wBAAuB,qCAAA,EAAsC,2BAAA,EAA4B,iCAAgC,yBAAA,EAA0B,0BAAA,EAA2B,6BAA4B,oBAAA,EAAqB,oBAAA,EAAqB,iCAAgC,qBAAA,EAAsB,2BAAA,EAA4B,uBAAsB,iCAAA,EAAkC,sBAAA,EAAuB,4BAAA,EAA6B,aAAA,EAAc,8BAAA,EAA+B,oCAAA,EAAqC,sBAAqB,+BAAA,EAAgC,oBAAA,EAAqB,0BAAyB,2BAAA,EAA4B,cAAA,EAAe,oBAAmB,iBAAA,EAAkB,qBAAA,EAAsB,kBAAiB,kBAAA,EAAmB,2BAAA,EAA4B,6BAA4B,8BAAA,EAA+B,yBAAA,EAA0B,yBAAwB,uBAAA,EAAwB,iBAAA,EAAkB,aAAY,QAAA,EAAS,yBAAA,EAA0B,aAAA,EAAc,0BAAA,EAA2B,+BAAA,EAAgC,kBAAA,EAAmB,4BAA2B,+BAAA,EAAgC,wBAAA,EAAyB,gBAAe,oBAAA,EAAqB,uBAAA,EAAwB,gCAA+B,+BAAA,EAAgC,+BAAA,EAAgC,2BAA0B,8BAAA,EAA+B,gBAAA,EAAiB,sBAAqB,YAAA,EAAa,aAAA,EAAc,oBAAmB,kCAAA,EAAmC,eAAA,EAAgB,uBAAsB,SAAA,EAAU,iBAAA,EAAkB,cAAA,EAAe,yBAAA,EAA0B,gBAAgB,CAAA;AAAA,EACp6I,UAAA,EAAY,CAAC,gBAAA,EAAiB,oBAAA,EAAqB,2BAAA,EAA4B,kBAAA,EAAmB,yBAAA,EAA0B,iBAAA,EAAkB,oBAAA,EAAqB,2BAAA,EAA4B,8BAAA,EAA+B,uBAAA,EAAwB,cAAA,EAAe,iBAAgB,cAAA,EAAe,gBAAA,EAAiB,cAAA,EAAe,oBAAA,EAAqB,yBAAA,EAA0B,0BAAA,EAA2B,gCAAA,EAAiC,0BAAA,EAA2B,8BAAA,EAA+B,oBAAA,EAAqB,eAAA,EAAgB,cAAA,EAAe,uBAAA,EAAwB,oBAAA,EAAqB,iBAAgB,uBAAA,EAAwB,uBAAA,EAAwB,yBAAA,EAA0B,mBAAA,EAAoB,oBAAA,EAAqB,8BAAA,EAA+B,yBAAA,EAA0B,wBAAA,EAAyB,uBAAA,EAAwB,gBAAA,EAAiB,eAAA,EAAgB,iBAAA,EAAkB,mBAAA,EAAoB,iBAAA,EAAkB,qBAAA,EAAsB,gCAAA,EAAiC,cAAA,EAAe,sBAAA,EAAuB,cAAA,EAAe,sBAAA,EAAuB,oBAAA,EAAqB,oBAAA,EAAqB,iBAAA,EAAkB,qBAAA,EAAsB,uBAAA,EAAwB,2BAAA,EAA4B,sBAAA,EAAuB,uBAAA,EAAwB,uBAAA,EAAwB,oBAAA,EAAqB,yBAAwB,sBAAA,EAAuB,sBAAA,EAAuB,wBAAA,EAAyB,kBAAA,EAAmB,2BAAA,EAA4B,iBAAA,EAAkB,sBAAA,EAAuB,kBAAA,EAAmB,cAAA,EAAe,2BAAA,EAA4B,wBAAA,EAAyB,iBAAA,EAAkB,wBAAA,EAAyB,uBAAA,EAAwB,qBAAA,EAAsB,yBAAA,EAA0B,wBAAA,EAAyB,oBAAA,EAAqB,kBAAA,EAAmB,kCAAA,EAAmC,+BAAA,EAAgC,gCAAA,EAAiC,kCAAA,EAAmC,gCAAA,EAAiC,gCAAA,EAAiC,8BAAA,EAA+B,4BAAA,EAA6B,gCAAA,EAAiC,kCAAA,EAAmC,uBAAA,EAAwB,sBAAqB,cAAA,EAAe,2BAAA,EAA4B,sBAAA,EAAuB,eAAA,EAAgB,yBAAA,EAA0B,uBAAA,EAAwB,gBAAA,EAAiB,cAAA,EAAe,WAAA,EAAY,cAAA,EAAe,iBAAA,EAAkB,kBAAA,EAAmB,oBAAA,EAAqB,WAAA,EAAY,kCAAA,EAAmC,gCAAA,EAAiC,yBAAA,EAA0B,yBAAA,EAA0B,8BAAA,EAA+B,kBAAA,EAAmB,aAAA,EAAc,kBAAA,EAAmB,sBAAA,EAAuB,8BAAA,EAA+B,kBAAA,EAAmB,oBAAA,EAAqB,aAAA,EAAc,sBAAA,EAAuB,eAAA,EAAgB,iBAAA,EAAkB,2BAA0B,mBAAA,EAAoB,iBAAA,EAAkB,mBAAA,EAAoB,kBAAA,EAAmB,kBAAA,EAAmB,qBAAA,EAAsB,qBAAA,EAAsB,sBAAA,EAAuB,wBAAA,EAAyB,gBAAA,EAAiB,sBAAA,EAAuB,mBAAA,EAAoB,kBAAA,EAAmB,2BAAA,EAA4B,+BAAA,EAAgC,kBAAA,EAAmB,oBAAA,EAAqB,qBAAA,EAAsB,kBAAA,EAAmB,aAAA,EAAc,iBAAA,EAAkB,mBAAA,EAAoB,yBAAA,EAA0B,YAAA,EAAa,oCAAA,EAAqC,YAAA,EAAa,iBAAA,EAAkB,sBAAA,EAAuB,cAAA,EAAe,qBAAA,EAAsB,oBAAmB,kBAAA,EAAmB,mBAAA,EAAoB,mBAAA,EAAoB,gBAAA,EAAiB,mBAAA,EAAoB,yBAAA,EAA0B,0BAAA,EAA2B,6BAAA,EAA8B,gCAAA,EAAiC,0BAAA,EAA2B,wBAAA,EAAyB,uBAAA,EAAwB,6BAAA,EAA8B,gCAAA,EAAiC,2BAAA,EAA4B,+BAAA,EAAgC,4BAAA,EAA6B,iCAAA,EAAkC,sBAAA,EAAuB,oBAAA,EAAqB,4BAAA,EAA6B,8BAAA,EAA+B,8BAAA,EAA+B,yBAAA,EAA0B,qBAAA,EAAsB,oBAAA,EAAqB,oBAAA,EAAqB,uBAAA,EAAwB,yBAAA,EAA0B,0BAAA,EAA2B,6BAA4B,4BAAA,EAA6B,kCAAA,EAAmC,8BAAA,EAA+B,kCAAA,EAAmC,4BAAA,EAA6B,4BAAA,EAA6B,uBAAA,EAAwB,oBAAA,EAAqB,sBAAA,EAAuB,uBAAA,EAAwB,wBAAA,EAAyB,2BAAA,EAA4B,8BAAA,EAA+B,yBAAA,EAA0B,2BAAA,EAA4B,4BAAA,EAA6B,0BAAA,EAA2B,0BAAA,EAA2B,uBAAA,EAAwB,8BAAA,EAA+B,4BAAA,EAA6B,0BAAA,EAA2B,6BAAA,EAA8B,0BAAA,EAA2B,2BAAA,EAA4B,2BAAA,EAA4B,wBAAA,EAAyB,2BAAA,EAA4B,4BAAA,EAA6B,oBAAA,EAAqB,2BAA0B,qBAAA,EAAsB,wBAAA,EAAyB,wBAAA,EAAyB,qBAAA,EAAsB,iBAAA,EAAkB,kBAAA,EAAmB,kBAAA,EAAmB,sBAAA,EAAuB,kBAAA,EAAmB,kBAAA,EAAmB,sBAAA,EAAuB,sBAAA,EAAuB,qBAAA,EAAsB,iBAAA,EAAkB,mBAAA,EAAoB,oBAAA,EAAqB,eAAA,EAAgB,iBAAA,EAAkB,oBAAA,EAAqB,iBAAA,EAAkB,sBAAA,EAAuB,cAAA,EAAe,eAAA,EAAgB,eAAA,EAAgB,eAAA,EAAgB,sBAAA,EAAuB,aAAA,EAAc,kBAAA,EAAmB,oBAAoB;AAC3/K","file":"generated.js","sourcesContent":["/* eslint-disable */\n/**\n * AUTO-GENERATED. DO NOT EDIT.\n *\n * Generated from: merged partner API, bundled Data API and mounted MCP Zod schemas\n * Generator: trillboards-api-client/scripts/generate.js\n *\n * Re-emit via `npm run generate`.\n */\n\n// OpenAPI version: 3.0.3\n// API title: Trillboards Partner API\n// API version: 1.2.0\n// Schema count: 194\n// Operation count: 242\n\n// ─── Component schemas ───────────────────────────────────────────────────\n\nexport type Ad = {\n /** Placement/allocation ID used for delivery accounting, make-goods, and pacing. */\n allocation_id?: string;\n /** Duration in seconds */\n duration?: number;\n /** Advertisement ID */\n id?: string;\n /** Server-generated impression ID that must be echoed when reporting proof-of-play. */\n impression_id?: string;\n /** **Deprecated alias of `tracking.impression_url`.** These two fields are the same string, byte for byte, produced from one value server-side — they are not two different hooks and firing both records the same call twice. Read `tracking.impression_url`; this flat field is retained only for clients that predate the `tracking` object and will be removed once they are off it. */\n impression_url?: string;\n /** The advertiser's own creative label from the booking record (its `title`, falling back to its `name`), carried through for your logs and QA. Omitted when the booking has neither. It is an internal label, not reviewed display copy, so do not surface it to viewers. */\n title?: string;\n /** Structured proof-of-play URLs for clients that prefer named tracking hooks over legacy flat fields. **Fire exactly one of `impression_url` / `complete_url` per play.** They resolve to the same endpoint and differ only by the `completed` query parameter, which that endpoint does not act on (see `GET /impression`) — calling both records the play twice. */\n tracking?: {\n /** Call when the ad completes playback/display dwell. This is `impression_url` plus `&completed=true`; the flag is inert server-side, so this is a delivery-accounting call, not proof of play. */\n complete_url?: string;\n /** Call when the ad is presented. Identical to the top-level `impression_url`. */\n impression_url?: string;\n /** **Server-signed proof of play. Present only on owner-billable direct bookings; absent on self-promo, house/QR, demo and fully-delivered placements.** When present, fire it VERBATIM — including the `sig`, `request_id` and `impid` parameters — once the ad has finished playing. Do not rewrite, re-sign, re-order, or drop any query parameter: the signature covers the values and any edit invalidates it. This is the only completion signal on the `data.ads` rail that settles; `complete_url` above is delivery accounting and is not billing-grade. Replays of the same play are collapsed on the SIGNED `request_id`, which carries this serve's `impression_id`. Because it is covered by `sig`, a replay that alters it to claim a fresh dedup key fails verification and records nothing — one served play can settle at most once. Firing it in addition to `complete_url` is correct and does not double-count — they land in different stores. */\n owned_direct_complete_url?: string;\n };\n /** `video` or `image`. On the partner/IMA rail an image instant-ad creative is substituted with its pre-rendered MP4 and reports `video`; seeing `image` here means that MP4 was not ready on this poll, so a video-only player should skip the entry and pick it up on a later poll rather than treat it as a still. */\n type?: \"video\" | \"image\";\n /** Media URL (CDN) */\n url?: string;\n};\n\nexport type AdSettings = {\n /** Partner-configured ad rotation cadence in seconds, served from the screen display setting `ad_interval_seconds` when present and legacy display preferences only as fallback. */\n ad_interval?: number;\n content_fit?: ContentFitSettings;\n /** Seconds to display image ads; independent from ad rotation cadence. */\n image_duration?: number;\n /** An ADVISORY delivery cap the client SDK is expected to honour — the server reports it and never enforces it anywhere. Sourced from the partner's `network_config.ad_delivery.max_ads_per_hour` (an integer 1-120), falling back to `sdk_config.max_ads_per_hour`; when neither is set the key is ABSENT from the response rather than null or defaulted. */\n max_ads_per_hour?: number;\n /** Label for the tap-to-continue overlay the partner Lite SDK draws over an ad. Read from `sdk_config.overlay_button_text` only — there is no runtime-config path for it — and the key is ABSENT when unset, in which case the SDK falls back to `TAP TO CONTINUE` client-side. */\n overlay_button_text?: string;\n /** Whether ad audio plays. It reads the typed `sound_enabled` column when that is non-null, otherwise `display_preferences.sound_enabled !== false` — so an unconfigured screen reports `true` and only an explicit `false` mutes. */\n sound_enabled?: boolean;\n};\n\n/**\n * Stateful resettable operating-system advertising identity. Send\n * `available` only with the current OS ID; send `limited`,\n * `consent_denied`, `unavailable`, `unsupported`, or `error` to clear a\n * previously observed identifier. UID2 and hashed email are audience\n * tokens, not OS advertising IDs, and do not belong in this envelope.\n *\n */\nexport type AdvertisingIdentity = {\n /** DISCARDED whenever `status` is `available` — the server stores `null` in that case, deliberately, because a resettable OS advertising ID stays valid until the OS rotates it and a collector's refresh or cache cadence is not identity expiry. It is only persisted for the non-`available` states. */\n expires_at?: string | null;\n /** Present only when status is `available`. */\n id?: string | null;\n /** Resettable OS identifier type. GAID maps to OpenRTB `aaid`. */\n id_type?: \"gaid\" | \"aaid\" | \"afai\" | \"rida\" | \"tifa\" | \"idfa\" | null;\n /** Must be false for `available` and true for `limited`. */\n limit_ad_tracking: boolean | null;\n /** When the device read the identifier from the OS. It is REQUIRED and strictly parsed — an unparseable or missing timestamp causes the entire identity envelope to be dropped without an error response, which is the most common silent rejection on this endpoint. */\n observed_at: string;\n /** Which platform API produced the identifier. `host_supplied` is the value the SERVER itself stamps when it synthesises an envelope from the legacy flat `advertisingId`/`limitAdTracking` fields or from a registration hardware tuple, so seeing it in a read does not mean the device claimed it. An unrecognised source rejects the whole envelope. */\n source: \"google_play_services\" | \"amazon_fire_os\" | \"roku_os\" | \"samsung_tizen\" | \"lg_webos\" | \"apple_platform\" | \"host_supplied\";\n /** Version of the platform component or SDK that read the identifier (e.g. the Play Services version), up to 128 characters, kept for provenance only. Trimmed, with a whitespace-only value stored as `null`. */\n source_version?: string | null;\n /** The state machine, not a label. `available` is accepted only with a valid `id`, an `id_type` and `limit_ad_tracking: false` — any other combination makes the server DISCARD the whole envelope behind a 200 and keep the previously stored identity. Every other value CLEARS a stored identifier, and `limited` additionally forces `limit_ad_tracking` to true. */\n status: \"available\" | \"limited\" | \"consent_denied\" | \"unavailable\" | \"unsupported\" | \"error\";\n};\n\n/**\n * Partner analytics summary (`@trillboards/api-client` `AnalyticsData`).\n *\n */\nexport type AnalyticsData = {\n /** **Never emitted** by the analytics endpoint, which returns impression counts and cached revenue only. */\n average_cpm: number;\n /** Optional per-screen breakdown. */\n by_screen?: Array<{\n earnings: number;\n impressions: number;\n name: string;\n screen_id: string;\n }>;\n /** Fraction of ad opportunities filled, 0..1. */\n fill_rate: number;\n /** The window this summary covers (e.g. day, week, month). */\n period: string;\n /** **Not emitted under this name.** The analytics endpoint returns `data.partner.active_devices`, derived from the screen's `online_status` via the partner device-status mapping, plus a `devices_by_status` breakdown. */\n screens_active: number;\n /** **Not emitted under this name.** The analytics endpoint returns `data.partner.total_devices`, a count of registered DEVICES rather than of screens — a device with no screen row still counts. */\n screens_total: number;\n /** **Not emitted.** `GET /v1/partner/analytics` returns `data.partner.total_revenue_cents` (integer US cents, read from the `partners.stats` cache) instead; that endpoint has no dollar-denominated earnings field. */\n total_earnings: number;\n /** Declared for the SDK type; `GET /v1/partner/analytics` does not return this shape. It returns `data.partner.total_impressions` — an ALL-TIME deduped `completed_impressions` count that ignores the `start_date`/`end_date` query parameters (those bound only `impressions_by_day`). */\n total_impressions: number;\n};\n\nexport type AudienceAnalyticsResponse = {\n /** `overview.total_impressions` counts `event_tracking` `start`/`impression` events — NOT the billable `completed_impressions` the dashboard and earnings endpoints use — and `active_screens` counts screens with ANY event in the window rather than screens that are online. **`trends.impressions_trend` and `trends.audience_trend` are hardcoded to the literal `stable`**, so the `increasing`/`decreasing` values are unreachable. `period` echoes `?period=` (`24h`/`7d`/`30d`; anything else silently means 7 days). */\n data?: {\n audience?: {\n avg_attention?: number;\n avg_face_count?: number;\n screens_with_data?: number;\n };\n overview?: {\n active_screens?: number;\n avg_impressions_per_screen?: number;\n total_impressions?: number;\n total_screens?: number;\n };\n period?: string;\n timestamp?: string;\n trends?: {\n audience_trend?: \"increasing\" | \"stable\" | \"decreasing\";\n impressions_trend?: \"increasing\" | \"stable\" | \"decreasing\";\n };\n };\n /** Per-request identifier (`req_` plus 32 hex), also returned on the `X-Request-Id` response header. */\n request_id?: string;\n /** Always `true` on a 2xx; failures return the error envelope (`success: false` plus an `error` object) at a 4xx/5xx status. */\n success?: boolean;\n};\n\n/**\n * Backend-derived predictive attention signal + buyer-grade attentive-seconds. 1:1 with audience_metrics. Never null by contract; placeholder on row 1.\n */\nexport type AudienceAttentionPrediction = {\n /** attentive_face_count SMALLINT: distinct viewers meeting the >=1s threshold; the buyer-side CPMA denominator. NEVER NULL. */\n attentive_face_count: number;\n /** attentive_seconds_observed = SUM(per_face[].gaze_seconds >= 1.0), OpenOOH 2024 4.2. NEVER NULL by DDL, so 0.000 means 'no viewer met threshold' ONLY when per_face_observations[] was present. NUMERIC(6,3). */\n attentive_seconds_observed: number;\n /** attentive_seconds_predicted_15s: expected next-15s attentive seconds from per-screen history x attention-weight; bootstrap = linear extrapolation. No global tau. NUMERIC(6,3). */\n attentive_seconds_predicted_15s: number;\n next_state_abs_error?: number | null;\n next_state_attention_forecast?: number | null;\n next_state_attention_realized?: number | null;\n next_state_confidence?: number | null;\n next_state_forecast_at?: string | null;\n next_state_graded_at?: string | null;\n next_state_horizon_minutes?: number | null;\n next_state_method?: string | null;\n next_state_persist_abs_error?: number | null;\n next_state_sample_size?: number | null;\n next_state_xscreen_abs_error?: number | null;\n next_state_xscreen_method?: string | null;\n next_state_xscreen_sample_size?: number | null;\n next_state_xsv_abs_error?: number | null;\n next_state_xsv_sample_size?: number | null;\n /** predicted_attention_next_30s enum {high,medium,low,none}. 'none' is below 'low'; fires when rolling history converges to zero or on a trend down from 'low'. */\n predicted_attention_next_30s: \"high\" | \"medium\" | \"low\" | \"none\";\n /** predicted_disengagement_imminent needs elevated dwell AND a falling trend, so it is unreachable whenever the history vector is empty. A 0% fire rate is a HISTORY-PLUMBING alarm, not a quiet fleet. */\n predicted_disengagement_imminent: boolean;\n prediction_evidence: string;\n};\n\n/**\n * Live/aggregate audience data for a screen\n * (`@trillboards/api-client` `AudienceData`).\n *\n */\nexport type AudienceData = {\n /** Count of attentive viewers in the sample. */\n attention_count: number;\n /** Optional demographic breakdown. */\n demographics?: Record<string, unknown>;\n /** Average dwell time for the sample. */\n dwell_time_avg: number;\n /** Declared for the SDK type. `GET /v1/partner/audience/live/{screenId}` puts `screen_id` at the TOP level of an `object: audience_live` envelope alongside `status` and `audience`, not inside this shape. */\n screen_id: string;\n /** **Not emitted under this name.** The live audience endpoint returns `last_updated` — the time of the newest underlying observation, nullable — and returns `audience: null` with `status: offline` when the screen has no live data at all. */\n timestamp: string;\n};\n\nexport type AudienceExportResponse = {\n /** One entry per RESOLVED screen (max 50 ids per call), emitted even for screens with no audience data — those get a zeroed `audience` block rather than being omitted. `impressions` counts `event_tracking` `start`/`impression` events, which are NOT the billable `completed_impressions` the dashboard and earnings endpoints use, and every row repeats the same request-level `period`. */\n data?: Array<{\n audience?: {\n avg_attention?: number;\n avg_face_count?: number;\n demographics?: Record<string, unknown>;\n income_profile?: Record<string, unknown>;\n lifestyle_segments?: Array<string>;\n };\n country?: string;\n impressions?: number;\n location?: string;\n period?: {\n end?: string;\n start?: string;\n };\n screen_id?: string;\n screen_name?: string;\n }>;\n /** Per-request identifier (`req_` plus 32 hex), also on the `X-Request-Id` header. Absent from the `format=csv` variant, which returns a bare five-column body and no envelope. */\n request_id?: string;\n /** Always `true` — a failure returns the error envelope (`success: false` plus an `error` object) at a 4xx/5xx status, so this field carries nothing the status code does not. */\n success?: boolean;\n /** `total_screens` counts the screens that RESOLVED, so it can be smaller than the id list you sent. `total_impressions` sums the same non-billable `start`/`impression` event counts. `aggregation` echoes the query parameter unvalidated (default `daily`) and does not change the shape of the response. */\n summary?: {\n aggregation?: string;\n period?: Record<string, unknown>;\n total_impressions?: number;\n total_screens?: number;\n };\n};\n\n/**\n * Per-device cross-day loyalty feature row emitted by the\n * `audience_loyalty_features` dataset. Keyed by a partner-scoped\n * pseudonymous ID — the same physical device yields a different ID\n * for each partner because the HMAC salt is partner-specific: one salt\n * per partner, whichever API key calls, so the ID is the same ref the\n * partner's device-level files carry.\n *\n */\nexport type AudienceLoyaltyFeaturesRow = {\n /** How the cross-day device key was formed. `primary_signal_source` is picked by a fixed precedence (`fixed_mac` > `mdns_vendor_model` > `wifi_bssid` > `ble_continuity` > `ble_mac_stable`) and `match_confidence` is a direct relabelling of it (first three `high`, `ble_continuity` `medium`, `ble_mac_stable` `low`) — not a computed probability. `wifi_bssid` marks a device observed over WiFi, whose key is its BSSID. */\n _match_disclosure?: {\n match_confidence?: \"high\" | \"medium\" | \"low\";\n primary_signal_source?: \"ble_continuity\" | \"ble_mac_stable\" | \"mdns_vendor_model\" | \"fixed_mac\" | \"wifi_bssid\";\n };\n /** NOT a per-device inference: it is the aggregate audience profile of the first screen in the device's visited screen set that has one, copied onto every device that touched that screen. All six sub-fields are `null` when no visited screen carries a profile. */\n demographic_inference?: {\n age_bracket?: string;\n attention_score_avg?: number;\n audience_attire_archetype?: string;\n audience_group_composition?: string;\n audience_intent_stage?: string;\n gender_distribution?: Record<string, unknown>;\n };\n /** Partner-scoped HMAC of cross-day device key (16 hex chars + tb_ prefix). Different partners get different IDs for the same physical device — preventing collusion. */\n device_id_pseudonymous: string;\n /** Distinct calendar days (ClickHouse server timezone) on which this device key was observed. This is the quantity behind the published `returning_visitor` qualification of two or more days. */\n distinct_days_in_window: number;\n /** Distinct SCREENS, not venues — a site with several screens contributes more than one, so this over-counts locations for any multi-screen venue. */\n distinct_venues_in_window?: number;\n /** Days since this device was first observed at any of the partner-scoped screens. */\n first_seen_relative_days: number;\n /** Whole days between now and the newest observation in the window, floored at 0 — `0` means seen today. An unparseable source timestamp also yields `0`, so it cannot be told apart from a same-day sighting. */\n last_seen_relative_days: number;\n /** Tier derived from the Bluetooth-SIG company identifier in the device's advertisement, not from any model string. Only five vendors carry an explicit tier (Apple / Samsung / Google `premium`, Microsoft `mainstream`, Sonos `audio_iot`); every other registered vendor, and every unresolvable id, falls to `other`. */\n manufacturer_class: \"premium\" | \"mainstream\" | \"audio_iot\" | \"other\";\n /** Vendor name resolved from the same Bluetooth-SIG company id. An id that resolves to no vendor returns the literal `Other tracked devices` rather than `null`, so an unknown device is not distinguishable by a null check. */\n manufacturer_label?: string;\n /** Raw cross-day-stable identifiers when available (NULL for randomized phone MACs without Continuity). */\n raw_identifiers?: {\n ble_mac?: string | null;\n /** First 3 bytes of BLE MAC (IEEE OUI vendor lookup). */\n ble_mac_oui?: string | null;\n ble_mac_oui_vendor?: string | null;\n manufacturer_company_id?: number | null;\n mdns_hostname?: string | null;\n wifi_bssid?: string | null;\n };\n /** Same arbitrary-row behaviour as `top_hour_of_day`, and the value comes from ClickHouse `toDayOfWeek` — **1 = Monday through 7 = Sunday**, which does not fit the declared 0..6 range. */\n top_day_of_week?: number;\n /** Produced by `argMax(toHour(observed_at), 1)`; because the ordering expression is the constant `1` every row ties, so this is the hour of an ARBITRARY observation in the window, not the modal hour. Do not read a peak out of it. */\n top_hour_of_day?: number;\n /** List of venue_category labels (Bar, Restaurant, etc.) the device was observed at. */\n venue_archetypes?: Array<string>;\n /** Count of raw radio OBSERVATIONS, not of visits — one dwelling device emits many rows per minute. */\n visit_count_in_window: number;\n};\n\nexport type AudienceMetricsResponse = {\n /** **Currently always the zero/`unknown` fallback.** The handler reads `metrics.live`, `metrics.aggregated` and `metrics.history`, but the underlying `getScreenMetrics` takes a time-range STRING and returns a FLAT object carrying none of those keys — so all three are undefined on every call and the hardcoded zeros are served. `period` echoes `?period=` (default `1h`) and is likewise not honoured downstream; `timestamp` is the response time, not a measurement time. */\n data?: {\n aggregated?: {\n avgAttention?: number;\n avgDwellMs?: number;\n avgFaceCount?: number;\n groupComposition?: Record<string, unknown>;\n incomeProfile?: {\n high?: number;\n low?: number;\n medium?: number;\n premium?: number;\n };\n };\n history?: Array<Record<string, unknown>>;\n live?: {\n /** Average attention score (0-1) */\n avgAttention?: number;\n /** Average faces detected */\n avgFaceCount?: number;\n incomeLevel?: \"unknown\" | \"low\" | \"medium\" | \"high\" | \"premium\";\n /** Maximum faces detected in period */\n maxFaces?: number;\n mood?: string;\n screenEngagement?: string;\n };\n period?: string;\n screen_id?: string;\n screen_name?: string;\n timestamp?: string;\n };\n /** Per-request identifier (`req_` plus 32 hex), also returned on the `X-Request-Id` response header. */\n request_id?: string;\n /** Always `true` on a 2xx; failures return the error envelope (`success: false` plus an `error` object) at a 4xx/5xx status. */\n success?: boolean;\n};\n\nexport type AudienceSegment = {\n /** Creation time of the authoritative PG row; the 201 create response falls back to the server clock only if the insert returned no row. */\n created_at?: string;\n /** Free text, trimmed on write. Defaults to the empty string, never `null`, when omitted at create. */\n description?: string;\n /** 24-character hex ObjectId minted by the API at create time and stored as the PG row's `mongo_id` — not the table's integer primary key. This is the value the get / patch / delete / match routes take as `:segmentId`. */\n id?: string;\n /** Stored and echoed but **nothing reads it** — `POST /segments/{id}/match` runs a segment regardless, and the list endpoint does not filter on it. Treat it as a caller-owned label, not a gate. */\n is_active?: boolean;\n /** When `POST /segments/{id}/match` last ran. The update is fire-and-forget after the response is sent, so a failed write leaves this stale or `null` with no error surfaced to the caller. */\n last_matched_at?: string | null;\n /** Number of SCREENS returned by the most recent `POST /segments/{id}/match`, capped by that call's `limit` (default 50, max 100) — so it is the size of the last result page, not the segment's reach. `null` until the first match call. */\n last_matched_count?: number;\n /** Trimmed on write, max 100 characters, and unique per partner — a repeat name is rejected `409 duplicate_segment_name` rather than upserting the existing segment. */\n name?: string;\n /** Always the literal `audience_segment`. `GET /audience/segments` wraps these in an `object: list` envelope, so the discriminator appears on both the envelope and each element. */\n object?: string;\n /** Per-request identifier, `req_` plus 32 hex characters, minted server-side and also returned on the `X-Request-Id` response header. A client-supplied `X-Request-Id` is NOT adopted — it is echoed back on `X-Client-Request-Id` instead. */\n request_id?: string;\n /** Predicates combined with AND, at most 20 per segment. `field` is restricted to a 17-name allowlist (`income_level`, `lifestyle_segment`, `attention_score`, `face_count`, `dwell_time_ms`, `crowd_density`, `ad_receptivity`, `emotional_engagement`, `purchase_intent`, `occupancy`, `noise_level`, `venue_type`, `country`, `state`, `city`, `hour_of_day`, `day_of_week`) and `operator` to `eq`/`neq`/`gt`/`gte`/`lt`/`lte`/`in`/`nin`/`contains`; any other key on a rule object is stripped on write. */\n rules?: Array<{\n field?: string;\n operator?: string;\n value?: unknown;\n }>;\n /** Last write to the segment, INCLUDING the fire-and-forget match-stats update — so running a match bumps this even though name, rules and `is_active` did not change. Omitted entirely from the 201 create response. */\n updated_at?: string;\n};\n\n/**\n * Buyer-grade social amplification signal (5 typed columns). Cloud Vertex Gemini producer. 1 row per audience observation when cascade is observable.\n */\nexport type AudienceSocialAttention = {\n amplification_evidence: string;\n cascade_depth: number;\n data_projection_version: number;\n data_projection_xid: string;\n /** All 5 columns NOT NULL except primary_attention_anchor_face_index (only legit NULL: no anchor or solo scene). */\n primary_attention_anchor_face_index?: number | null;\n /** NUMERIC(4,3). */\n social_amplification_factor: number;\n /** viral_attention_score is the headline number Viant/T-Vision license. Composite of social_amplification_factor × cascade_depth × emotional engagement. NUMERIC(4,3). */\n viral_attention_score: number;\n};\n\nexport type BatchResult = {\n /** Number that failed */\n failed_count?: number;\n /** Not emitted. The live response names this array `errors`, and each entry carries `screen_id`, a human `error` string, and an additional machine `code`: `screen_not_found` for an id that is not 24-hex or that your partner account does not own, `write_failed` for a database failure. Read `errors`. */\n failed_screens?: Array<{\n error?: string;\n screen_id?: string;\n }>;\n /** Constant discriminator `batch_result`, shared verbatim by every partner batch endpoint (content preferences, display settings, programmatic settings) — it does not identify which one you called. */\n object?: string;\n /** Server-minted per HTTP request in the form `req_<32 hex>`, also returned in the `X-Request-Id` response header; omitted if no request id was assigned to the call. Quote it when reporting a partial batch failure — it is the only handle that ties your call to the per-screen error rows. */\n request_id?: string;\n /** Number of screens requested to update */\n total_requested?: number;\n /** Number successfully updated */\n updated_count?: number;\n /** IDs of successfully updated screens */\n updated_screens?: Array<string>;\n};\n\n/**\n * Partner billing snapshot — plan, credits, and month-to-date usage\n * (`@trillboards/api-client` `BillingInfo`).\n *\n */\nexport type BillingInfo = {\n /** **Never emitted**, so its unit is undefined. The credits that do exist are bought through `POST /billing/credits`, which reports `credits_added_cents` — integer US cents, not an abstract credit count. */\n credits_remaining: number;\n /** **Never emitted.** Consumption is reported by `GET /billing/usage` (`object: billing_usage`), which returns the billing service's usage summary rather than a credit counter. */\n credits_used: number;\n /** **No producer.** There is no `GET /v1/partner/billing` route; the live billing surface is `/billing/plans`, `/billing/subscribe`, `/billing/cancel`, `/billing/usage` and `/billing/subscription`, each returning a Stripe-style envelope tagged with an `object` marker plus `request_id`. */\n plan: string;\n /** Whether the partner has a connected Stripe account. */\n stripe_connected: boolean;\n /** Usage counters for the current billing month, keyed by metric. */\n usage_this_month: Record<string, number>;\n};\n\nexport type BleScanResultData = {\n /** Despite the name this is a GENERALISED advert-subtype code, and the server stores it as `manufacturer_subtype`. Values 0x00-0x1F are genuine Apple Continuity subtypes; 0x80 and above are SYNTHETIC markers this parser assigns to non-Apple formats — 0x80 iBeacon, 0x81 Eddystone-UID, 0x82 Fast Pair, 0x83 Tile, 0x85 Eddystone-URL, 0x86 AltBeacon. Note an Apple iBeacon reports 0x80, not its literal Apple subtype 0x02. */\n appleContinuitySubtype?: number | null;\n /** The Android `BluetoothDevice.getType()` constant: 0 UNKNOWN, 1 CLASSIC, 2 LE, 3 DUAL. Treat `0` as \"unknown OR the read threw\" — the producer catches any exception and substitutes 0 rather than omitting the field. */\n deviceType?: number | null;\n /** iBeacon major value, an unsigned 16-bit integer (0-65535) parsed BIG-endian from the advert. `null` unless a valid iBeacon frame was found, and meaningless without the accompanying `ibeaconUuid`. */\n ibeaconMajor?: number | null;\n /** iBeacon minor value, unsigned 16-bit big-endian (0-65535). `null` outside a valid iBeacon frame; major and minor are only comparable within one `ibeaconUuid` namespace. */\n ibeaconMinor?: number | null;\n /** The 16-byte iBeacon proximity UUID as canonical dashed lowercase, parsed BIG-endian. Present only for a well-formed Apple iBeacon frame — an Apple subtype 0x02 whose inner length is not 0x15 is explicitly rejected rather than partially parsed. */\n ibeaconUuid?: string | null;\n /** The Bluetooth SIG assigned company identifier from the advert's Manufacturer Specific Data structure (AD type 0xFF), read little-endian — e.g. 76 (0x004c) Apple, 6 Microsoft, 224 (0x00e0) Google. `null` when the record carries no such structure. One synthetic exception: a Google Fast Pair service-data advert is forced to the Google id even though it has no 0xFF structure. */\n manufacturerCompanyId?: number | null;\n /** The BLE advertiser address exactly as Android reports it, unhashed on the device. It is sent over TLS, and Trillboards stores it as received and as a keyed HMAC-SHA256 hash. On most modern phones this is a resolvable private address that ROTATES roughly every 15 minutes, so it is not a stable device identity; the sibling `addr_type` (`public`/`random`/`anonymous`) is what tells the two apart. */\n rawAddress: string;\n /** Received signal strength in dBm (negative, bounded -120..0). It is the MAXIMUM RSSI across every advertisement from this address inside the scan window — the closest observed reading, not a mean — because the scanner groups repeated adverts per address and keeps the strongest as the representative. */\n rssi: number;\n /** Advertised GATT service UUIDs, de-duplicated. 16-bit UUIDs are rendered as FOUR lowercase hex characters (e.g. `feaa`), not expanded to the full `0000feaa-0000-1000-8000-00805f9b34fb` base form; 128-bit UUIDs are canonical dashed lowercase. `null` rather than `[]` when the advert carries none. */\n serviceUuids?: Array<string> | null;\n /** Lowercase hex, no separators, of ONLY the advert sub-bytes that survive address rotation (an iBeacon UUID+major+minor, an Eddystone namespace+instance, a Fast Pair model id…), prefixed with the synthetic subtype byte. Calibration bytes such as TX power are deliberately excluded. It is `null` far more often than it is present: 13 rotating Apple subtypes are dropped outright and unknown Apple subtypes fail closed, so a populated `appleContinuitySubtype` with a null payload here is normal. */\n stableManufacturerPayloadHex?: string | null;\n /** Advertised transmit power in dBm (bounded -127..20) taken from the advert itself, NOT from `ScanResult.getTxPower()`. For iBeacon and Eddystone frames it is the format's calibrated 1-metre reference power, which overwrites the generic AD-type-0x0A value; `null` when the advert carries neither. */\n txPowerDbm?: number | null;\n [key: string]: unknown;\n};\n\nexport type CanonicalError = {\n error: {\n /** Stable machine-readable error code. Switch on this, never on `message`. The 143 CATALOGUED codes are published as the `ErrorCode` schema and served live by `GET /v1/errors`, which is the authority. This field is NOT limited to them: services emit further codes directly, and `ApiError.notFound(resource)` mints `<resource>_not_found` from an arbitrary resource name. Always handle an unrecognised code by falling back to `type`. */\n code: string;\n /** Per-field violations (populated from Zod errors). */\n details?: Array<{\n code?: string;\n message?: string;\n param?: string;\n }>;\n /** Link to the documentation for this error code (https://api.trillboards.com/docs/errors#<code>). */\n doc_url?: string;\n /** Agent-facing hints and suggestions. */\n help?: {\n portal_url?: string;\n suggestions?: Array<string>;\n summary?: string;\n try_next?: string;\n };\n /** Human-readable error message. Safe to surface to end-users. */\n message: string;\n /** Offending parameter name (when applicable). */\n param?: string;\n /** Echoed X-Request-Id for correlation. */\n request_id: string;\n /** Stripe-style error taxonomy (derived from utils/errorCatalog). */\n type: \"api_error\" | \"authentication_error\" | \"idempotency_error\" | \"invalid_request_error\" | \"permission_error\" | \"rate_limit_error\";\n };\n};\n\nexport type ChangelogEntry = {\n /** ISO date string or null for unreleased */\n date: string | null;\n /** Human-readable change description */\n description: string;\n /** API surface: data, sdk, device, sensing, openrtb, partner, all */\n surface: string;\n /** Keepachangelog type: added, changed, deprecated, removed, fixed, security */\n type: string;\n /** \"unreleased\" or a date like \"2026-04-16\" */\n version: string;\n};\n\nexport type ChangelogResponse = {\n /** Matching changelog entries */\n data: Array<ChangelogEntry>;\n /** Always \"list\" */\n object: \"list\";\n /** Number of entries returned */\n total: number;\n};\n\n/**\n * Completed-play observations from VAST complete and proof-of-play paths. Deduped by issued request/impression key for live revenue and daily earnings.\n */\nexport type CompletedImpressions = {\n /** NUMERIC(18,6). */\n actual_cpm?: number | null;\n /** NUMERIC(18,6). */\n actual_gross_revenue?: number | null;\n ad_id: string;\n advertiser_domain?: string | null;\n bid_request_id?: string | null;\n billable_key?: string | null;\n buyer_seat?: string | null;\n completed_at: string;\n /** Creative identity is NOT content_sha256 (35 of 430,165 ClickHouse rows since 2026-10-20): join creative_identity on the row's keys (identityKeys.identityJoinSql); its sidecar has the current and ti... */\n content_sha256?: string | null;\n /** chEventParityCron: exact-key repair; created_at/oldest authorized floor; original completed_at CH key; same writer/DLQ before ACK; no delivery debit. All leaf pending indexes must be valid; failed/... */\n created_at: string;\n creative_id?: string | null;\n currency: string;\n demand_partner_id?: number | null;\n device_id?: string | null;\n /** NUMERIC(5,4). */\n display_area_ratio?: number | null;\n display_mode?: string | null;\n /** NUMERIC(10,2). */\n duration_seconds?: number | null;\n earnings_deal_id?: string | null;\n earnings_deal_type?: string | null;\n earnings_source?: string | null;\n event_clock_skew_seconds?: number | null;\n event_timestamp_clamped?: boolean | null;\n givt_passed?: boolean | null;\n /** JustBaat dedupes issued request keys; Adipolo/VidVerto/others use rendered impression_id with request fallback. Readers/rollups dedupe overlapping vast_event, proof_of_play and approved partner_sdk. */\n id?: number | null;\n impression_id?: string | null;\n /** NUMERIC(12,6). */\n impression_multiplier: number;\n ingestion_path: string;\n /** SDK missing-price is server-pending until signed native pricing; directbillable_ is not authority. Client debit/mirror/pending claims are stripped. Legacy finalization: recorded/native nonnull zero... */\n metadata: Record<string, unknown>;\n normalized_event_timestamp?: string | null;\n play_id?: string | null;\n /** PG is the canonical completion write; completed_impressions_dist is its CH reporting mirror. pricing_source: 'placement_purchase'=ad_price/goal per play; '*_overdelivered'/'*_zero'=$0; 'config_defa... */\n pricing_source: string;\n quantity_measured_at?: string | null;\n quantity_source?: string | null;\n quantity_source_type?: number | null;\n quantity_vendor?: string | null;\n raw_client_timestamp?: string | null;\n /** owned_direct = signed src or verified SDK. VAST impid, request_id and SDK suffix share one nonce/PG row; completion, exact debit and eligible billing commit together. First authorized write owns CH... */\n request_id?: string | null;\n screen_id?: number | null;\n screen_mongo_id: string;\n serving_tenant_id?: number | null;\n signature?: string | null;\n /** ImpressionController drops rows with unknown source_provider (PR #2924); earnings use partner billing policy, not raw counts. Exclude pi_e2e_*, admin-allocation and is_demo_data from revenue. */\n source_provider: string;\n variant_id?: number | null;\n variant_name?: string | null;\n zone_id?: string | null;\n};\n\n/**\n * Per-content-type sizing returned and accepted by the Partner API.\n * `fit` preserves the entire creative and may letterbox; `fill` covers\n * the screen and may crop edges. Omitting this object from a write leaves\n * the screen's current behavior unchanged.\n *\n */\nexport type ContentFitSettings = {\n /** Sizing for direct and programmatic advertisements. */\n advertisements?: \"fit\" | \"fill\";\n /** Sizing for default/background streams. */\n default_streams?: \"fit\" | \"fill\";\n /** Sizing for self-promotion and QR-code house ads. */\n self_promo?: \"fit\" | \"fill\";\n};\n\nexport type ContentPreferencesInput = {\n /** Specific advertiser IDs to block */\n blocked_advertisers?: Array<string>;\n /** Ad categories to block (e.g., \"alcohol\", \"gambling\", \"adult\") */\n blocked_categories?: Array<string>;\n /** Max times same ad can show per hour */\n max_ad_frequency?: number;\n /** Minimum ad content rating */\n min_ad_rating?: \"G\" | \"PG\" | \"PG-13\" | \"R\";\n /** Preferred ad categories (prioritized in auctions) */\n preferred_categories?: Array<string>;\n};\n\nexport type ContentPreferencesResponse = {\n /** NOT EMITTED. No handler on this endpoint produces this key — neither `formatContentPreferences` nor the service feeding it has such a field. Advertiser blocking lives on the programmatic-settings resource instead, as `blocked_advertisers` on `GET/PUT /v1/partner/screens/{screenId}/programmatic-settings`. */\n blocked_advertisers?: Array<string>;\n /** The screen's own blocked list, `[]` when unset. Matched at approval time against the ad's typed `iab_categories` with `categoriesMatch`, so IAB codes and human-readable names both work. It is the only one of this schema's five list/rating fields the server actually emits. */\n blocked_categories?: Array<string>;\n /** NOT EMITTED, and the identifier does not occur anywhere in the API source. The only delivery cap this platform exposes to partners is the advisory `max_ads_per_hour` in the `/ads` response settings. */\n max_ad_frequency?: number;\n /** NOT EMITTED, and the identifier `min_ad_rating` does not occur anywhere in the API source. The response carries `max_age_rating` instead — an UPPER bound on the ad classification (`G`..`NC-17`), defaulting to `PG-13`, not a minimum. */\n min_ad_rating?: string;\n /** Always the literal `content_preferences`. This response is FLAT — there is no `success`/`data` envelope, unlike the L-bar and default-stream routes — and `POST .../content-preferences/apply-preset` returns this same object with an extra `preset_applied` field appended. */\n object?: string;\n /** NOT EMITTED, and the identifier does not occur anywhere in the API source. The nearest real control is `allowed_categories`, which the formatter does return and which acts as a scoring boost (+10 per match) rather than a hard preference. */\n preferred_categories?: Array<string>;\n /** Echo of the server-assigned `req.requestId`. The formatter attaches it only when one exists, so the key is ABSENT rather than null on a request that carried none. */\n request_id?: string;\n /** Echoed straight back from the `{screenId}` path parameter — the formatter is handed the path value, not a column — so it always equals what you asked for. */\n screen_id?: string;\n /** ISO-8601, but it is the SCREEN row's `updatedAt`, not a preferences-specific timestamp, so unrelated screen edits move it. When the screen has none the formatter substitutes the current time, so this field is never null and never proves a write landed. */\n updated_at?: string;\n};\n\nexport type ContentPreset = {\n /** Advertiser IDs blocked */\n blocked_advertisers?: Array<string>;\n /** Categories blocked by this preset */\n blocked_categories?: Array<string>;\n /** Preset description */\n description?: string;\n /** Unique preset identifier */\n id?: string;\n /** Minimum ad rating allowed */\n min_ad_rating?: \"G\" | \"PG\" | \"PG-13\" | \"R\";\n /** Human-readable preset name */\n name?: string;\n /** Full preference object applied by this preset */\n preferences?: Record<string, unknown>;\n};\n\nexport type CreatePartnerSensingRouteRequest = {\n /** The channel actions fired on a match; at least 1 and at most 10, each carrying exactly the destination its channel requires. A route with zero actions cannot exist, so use a `webhook` action for anything you do not want delivered to a person. */\n actions: Array<SensingRouteAction>;\n /** Seven-bit weekday mask evaluated in `activeTz`: bit 0 Monday through bit 6 Sunday, so the default 127 is every day and 0 means the route never fires. Requires `activeTz`. */\n activeDaysMask?: number;\n /** Inclusive end of the daily active window, minutes past local midnight in `activeTz` (0..1439). Set it BELOW `activeStartMinute` for an overnight window — 1200 to 360 is 20:00 through 06:00. */\n activeEndMinute?: number;\n /** Inclusive start of the daily active window, minutes past local midnight in `activeTz` (0..1439; 1200 = 20:00). Has no effect without `activeTz`, and omitting either end makes the window whole-day. */\n activeStartMinute?: number;\n /** IANA zone name (e.g. `America/New_York`) the active window is evaluated in, trimmed to 64 characters. Omitting it makes `activeStartMinute`, `activeEndMinute` and `activeDaysMask` inert — the route is then always active — and an unparseable zone fails open. */\n activeTz?: string;\n condition?: SensingRouteCondition;\n /** Minimum gap in seconds between alerts for the same (route, screen) episode; clamped to 0..86400, default 300. It also bounds the falling-edge clear window at 60 s, so a value below that re-arms the route faster too. */\n debounceSeconds?: number;\n /** Required when mode=digest. `field` is required for avg/max. */\n digest?: {\n field?: string;\n intervalMinutes?: number;\n metric?: \"count\" | \"avg\" | \"max\";\n windowMinutes?: number;\n };\n /** Only the JSON literal `false` creates the route disabled — any other value, including `0` and the string `\"false\"`, creates it enabled. Note the update endpoint inverts this test, so send a real boolean on both. */\n enabled?: boolean;\n /** `threshold` (the default) needs a `condition` and evaluates every incoming observation. `digest` needs a `digest` block instead, sends on a schedule, and rejects `screenFocusRegions` and explicit review timing. Only the exact string `digest` selects digest mode; anything else falls back to `threshold`. */\n mode?: \"threshold\" | \"digest\";\n /** Operator-supplied label, required, trimmed and capped at 200 characters. It is delivered as `route_name` on every fire and shown in operator surfaces; it is not the alert text. */\n name: string;\n /** Bind to a named Sense Anything profile (stable across redeploys). */\n profileName?: string;\n reviewTimingMode?: SensingRouteReviewTimingMode;\n reviewWaitSeconds?: SensingRouteReviewWaitSeconds;\n /** Optional per-camera rectangles or polygon lassos. Every entry must reference screenMongoIds. Supported only for visual threshold alerts using once_per_episode, on_escalation, or nag_until_resolved cadence; not digest, group-targeted, non-visual, or per_new_entity routes. */\n screenFocusRegions?: Array<SensingRouteScreenFocusRegion>;\n /** Screens to watch — all owned by this partner (foreign → 403). Provide EITHER screenMongoIds OR targetGroupId, not both. */\n screenMongoIds?: Array<string>;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainMinSamples?: number;\n /** How long the condition must hold before the FIRST alert, in seconds (600 = ten minutes). Omit to alert on the first true reading. Cadence-independent, because the evaluator compares timestamps - correct however often the screen reports, including when that rate changes while the condition holds. */\n sustainSeconds?: number;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainWindowSeconds?: number;\n /** A sg_ Screen Group id (owned by this partner) to target instead of an explicit screen set. The route fires on the group's live members. Requires SENSING_ROUTES_GROUP_TARGET_ENABLED. */\n targetGroupId?: string;\n};\n\n/**\n * Structured creative classification — the same shape the Trillboards venue portal renders for a served ad.\n */\nexport type CreativeClassification = {\n /** The name whoever FIRST submitted these bytes passed on `POST /creatives`, carried through the moderation queue. It is unverified free text, and because the review row is keyed on the content hash a later submitter of the same bytes reads the first submitter's value. `null` when none was supplied. */\n advertiser_name?: string | null;\n /** One of G, PG, PG-13, R, NC-17 (null until classified). */\n age_rating?: string | null;\n /** Descriptive output of the same classifier pass — visible subjects, keywords, scene setting, mood, a quality grade, and `text_content` recovered by OCR. Every field is `null`/empty until classification finishes. This is scene description, not the moderation verdict; that is `status` plus `moderation.flags`. */\n analysis?: {\n keywords?: Array<string>;\n mood?: string | null;\n quality?: string | null;\n setting?: string | null;\n subjects?: Array<string>;\n /** On-screen text (OCR). */\n text_content?: string | null;\n };\n /** Classifier output: `brand`, `category` and IAB category codes. All three are `null`/empty while `status` is `pending`, so an empty `iab_categories` means not yet classified rather than 'no categories apply'. */\n categorization?: {\n brand?: string | null;\n category?: string | null;\n iab_categories?: Array<string>;\n };\n /** Classification confidence, 0..1. */\n confidence?: number | null;\n /** SHA-256 of the bytes the SERVER fetched from your `media_url` — lowercase 64-hex, never a hash you declare — and `id` is this same value prefixed `sha256:`. Classification is content-addressed and global, so two partners submitting identical bytes share one record, and this is the byte-identity join key into `completed_impressions` and `vast_requests`. */\n content_sha256?: string;\n /** Canonical reference — `sha256:<hex>` of the creative bytes. Use as `creative_ref` when posting plays. */\n id?: string;\n /** `approved` is not an independent verdict — it is derived as `status === 'approved'`, so it is false for both `pending` and `blocked` and adds no information beyond `status`. `flags` is the classifier's reason list and is empty until classification finishes. */\n moderation?: {\n approved?: boolean;\n flags?: Array<string>;\n };\n /** Constant discriminator `creative_classification`. It is returned identically on the 202 (queued) and 200 (already classified) forms of this response, so it tells you nothing about whether classification has finished — read `status` for that. */\n object?: string;\n /** `pending` means queued and not yet classified. `approved`, `flagged` and `blocked` are terminal: re-submitting the same bytes then returns 200 immediately without re-running the classifier or consuming your daily classification quota, whereas a still-pending submit returns 202. The value is passed through from the shared review row unmapped, so a state outside this enum (e.g. `metadata_only`) can surface. */\n status?: \"pending\" | \"approved\" | \"flagged\" | \"blocked\";\n /** Always `null` in practice: the only writer of the underlying `creative_reviews.thumbnail_url` column has no remaining callers, so nothing populates it. When it did carry a value it was constrained to a Trillboards CDN host, so an off-host thumbnail was dropped rather than passed through. */\n thumbnail_url?: string | null;\n};\n\n/**\n * Native readiness hint delivered through existing partner SSE and the audited webhook queue. The hint is free; only the canonical reward page uses native paid admission.\n */\nexport type CreativeRewardsReadyEvent = {\n data: {\n advertisement_id: string;\n /** Lossless native build stamp. */\n build_stamp: string;\n builder_version: string;\n experiment_id: string;\n from: string;\n notification_id: string;\n object: \"creative_reward_ready\";\n recovery: \"rewind_and_dedupe\";\n request_idempotency_key_required: true;\n /** Canonical native reward path plus date scope; never contains credentials or a paid request key. */\n reward_reader_url: string;\n to: string;\n view: \"latest_state\";\n };\n event: \"creative.rewards_ready\";\n /** Audited webhook notification ID; SSE data carries the same ID inside data.notification_id. */\n id?: string;\n timestamp: string;\n};\n\nexport type DashboardResponse = {\n /** All amounts are integer US cents. `total_earned_cents` is lifetime earner revenue across every status (estimated + confirmed + paid); `available_payout_cents` is only the Net-60-matured payable balance minus anything already reserved, floored at 0; `next_payout_date` is simply the 1st of next month, computed in the handler with no reference to a scheduled transfer. `revenue_share_percent` is 0-100 and reads `60` when no `publisher_payout_rates` rule applies. */\n earnings?: {\n available_payout_cents?: number;\n next_payout_date?: string;\n pending_payout_cents?: number;\n revenue_share_percent?: number;\n total_earned_cents?: number;\n };\n /** Identity block copied off the partner row, where `created_at` is the registration timestamp. The row is read through a 10-minute Redis cache, so a just-changed name or status can lag by that much. */\n partner?: {\n created_at?: string;\n name?: string;\n slug?: string;\n status?: \"pending\" | \"active\" | \"suspended\" | \"terminated\";\n };\n /** The ten newest `impression_ledger` rows for the partner, newest first. `type` is always the literal `impression` — no other activity kind is ever emitted. */\n recent_activity?: Array<{\n ad_id?: string;\n screen_id?: string;\n timestamp?: string;\n type?: string;\n }>;\n /** `active_screens`/`offline_screens` count `online_status` `online`/`offline` from the live heartbeat mirror — a screen that has never heartbeated reads `unknown` and lands in NEITHER bucket, so the two need not sum to `total_screens`. `pending_screens` is the separate approval status and overlaps both. The three impression totals are deduped, GIVT-passed `completed_impressions` over midnight-today / the last 7 days / month-to-date, all cut in the API server's local timezone rather than the screen's. */\n stats?: {\n active_screens?: number;\n offline_screens?: number;\n pending_screens?: number;\n total_impressions_month?: number;\n total_impressions_today?: number;\n total_impressions_week?: number;\n total_screens?: number;\n };\n /** `connect_account_id` is **redacted**: it is the literal string `***` when an account is connected and `null` when it is not, so it can never be used as an identifier. `account_status` defaults to `pending` and must be `active` before a payout request is accepted. */\n stripe_status?: {\n account_status?: string;\n connect_account_id?: string;\n };\n /** The five screens with the most completed impressions since midnight today (server timezone). `name` falls back to the literal `Unknown Screen`, and `location` is a `City, State` string or `null` when either part is missing. */\n top_screens?: Array<{\n impressions_today?: number;\n location?: string;\n name?: string;\n screen_id?: string;\n }>;\n};\n\nexport type DataApi_AdSightingRecord = {\n /** The words read on the ad. */\n ad_text: string | null;\n advertiser_as_read: string | null;\n board_id: string | null;\n brand_as_read: string | null;\n brand_id: string | null;\n brand_name: string | null;\n cab_heading_deg: number | null;\n cab_lat: number | null;\n cab_lon: number | null;\n capture_mode: string | null;\n confidence: number | null;\n face_lat: number | null;\n face_lon: number | null;\n flight_end: string | null;\n flight_label_kind: string | null;\n flight_open: boolean | null;\n flight_passes: number | null;\n flight_reads: number | null;\n flight_start: string | null;\n /** The ad box in the frame, fractions of the frame. */\n frame_box_x0: number | null;\n frame_box_x1: number | null;\n frame_box_y0: number | null;\n frame_box_y1: number | null;\n frame_expires_at: string | null;\n /** A signed, read-only link to the whole still the ad was read off. */\n frame_url: string | null;\n /** ssr-v1, vlmtext-v1 or edge_ocr_v1. */\n instrument_version: string | null;\n /** The face carried no advertiser. */\n is_vacancy: boolean;\n legibility: string | null;\n /** Legible, Partial or Illegible. */\n legibility_label: string | null;\n market_code: string | null;\n market_country: string | null;\n market_key: string | null;\n market_name: string | null;\n match_basis: string | null;\n match_distance_m: number | null;\n /** matched | unmatched | no_candidate. */\n match_status: string | null;\n orientation_idx: number | null;\n parent_name: string | null;\n /** The ad (on a close-up, its words) inside the photo, fractions of the photo. */\n photo_box_x0: number | null;\n photo_box_x1: number | null;\n photo_box_y0: number | null;\n photo_box_y1: number | null;\n photo_expires_at: string | null;\n /** The ad itself: closeup = the capture-resolution close-up a tablet uploaded; crop = the ad cut from the frame at request time (its box padded 50% per side), never stored. */\n photo_kind: \"closeup\" | \"crop\" | null | null;\n /** A short-lived link to the photo (a signed capability for a crop). */\n photo_url: string | null;\n product_as_read: string | null;\n publisher_name: string | null;\n /** The still instant, UTC. */\n seen_at: string;\n sighted_creative_id: string | null;\n /** Stable id of the sighting (32 hex). */\n sighting_id: string;\n source: string | null;\n surface: string | null;\n /** The surface as a buyer reads it (Billboard, Digital board, Storefront, ...). */\n surface_label: string | null;\n ticker: string | null;\n vehicle_side: string | null;\n};\n\nexport type DataApi_AdSightingsHistoryResponse = {\n data: {\n next_cursor: string | null;\n /** One row per month, market and brand (creative_brand_history): flights active and started, creatives launched, board faces and points, share of voice over every ad flight in the market (registered faces and unregistered points), and the market vacancy, each with its counts. */\n rows: Array<Record<string, unknown>>;\n };\n meta: {\n metric: \"creative_brand_history\";\n product: \"ad_sightings\";\n records: number;\n request: Record<string, unknown>;\n view: \"history\";\n };\n success: true;\n};\n\nexport type DataApi_AdSightingsResponse = {\n data: {\n next_cursor: string | null;\n rows: Array<DataApi_AdSightingRecord>;\n };\n meta: {\n product: \"ad_sightings\";\n records: number;\n request: Record<string, unknown>;\n };\n success: true;\n};\n\nexport type DataApi_AggregateResponse = {\n aggregations: Array<Record<string, unknown>>;\n group_by: string;\n metric: string;\n time_window: string;\n};\n\nexport type DataApi_Error = {\n error: {\n /** Stable machine-readable error code. Switch on this, never on `message`. The 143 CATALOGUED codes are published as the `ErrorCode` schema and served live by `GET /v1/errors`, which is the authority. This field is NOT limited to them: services emit further codes directly, and `ApiError.notFound(resource)` mints `<resource>_not_found` from an arbitrary resource name. Always handle an unrecognised code by falling back to `type`. */\n code: string;\n /** Per-field violations (populated from Zod errors). */\n details?: Array<{\n code?: string;\n message?: string;\n param?: string;\n }>;\n /** Link to the documentation for this error code (https://api.trillboards.com/docs/errors#<code>). */\n doc_url?: string;\n /** Agent-facing hints and suggestions. */\n help?: {\n portal_url?: string;\n suggestions?: Array<string>;\n summary?: string;\n try_next?: string;\n };\n /** Human-readable error message. Safe to surface to end-users. */\n message: string;\n /** Offending parameter name (when applicable). */\n param?: string;\n /** Echoed X-Request-Id for correlation. */\n request_id: string;\n /** Stripe-style error taxonomy (derived from utils/errorCatalog). */\n type: \"api_error\" | \"authentication_error\" | \"idempotency_error\" | \"invalid_request_error\" | \"permission_error\" | \"rate_limit_error\";\n };\n};\n\n/**\n * The 143 CATALOGUED error codes, generated from utils/errorCatalog.js — the same catalog that backs https://api.trillboards.com/docs/errors. This is the documented registry, NOT the complete set of codes the API can emit: a response `code` is an open string (see `TrillboardsError`). Use this list to recognise a code, never to assume a response cannot carry another.\n */\nexport type DataApi_ErrorCode = \"AUTH_REQUIRED\" | \"BRAND_CONFIGURATION_FAILED\" | \"BRAND_PROFILE_CONFLICT\" | \"BRAND_PROFILE_NOT_FOUND\" | \"CREATIVE_ADVERTISEMENT_NOT_OWNED\" | \"CREATIVE_MODERATION_UNAVAILABLE\" | \"CREATIVE_NOT_LINKED\" | \"CREATIVE_NOT_READY\" | \"CREATIVE_POLICY_CONFLICT\" | \"CREATIVE_TOO_LARGE\" | \"EXPERIMENT_COMPLETED\" | \"EXPERIMENT_NOT_FOUND\" | \"FEEDBACK_POLICY_REQUIRED\" | \"IDEMPOTENCY_CONFLICT\" | \"INVALID_BRAND_RULES\" | \"INVALID_CREATIVE_URL\" | \"INVALID_CREATIVE_WEIGHTS\" | \"INVALID_FEEDBACK_CREATIVE\" | \"INVALID_FEEDBACK_OBJECTIVE\" | \"INVALID_FEEDBACK_REQUEST\" | \"INVALID_POLICY_VERSION\" | \"INVALID_VARIANTS\" | \"MEDIA_BUY_NOT_FOUND\" | \"OPERATION_ALREADY_COMMITTED\" | \"OPERATION_IN_PROGRESS\" | \"POLICY_NOT_AVAILABLE\" | \"SOURCE_CREATIVE_NOT_FOUND\" | \"UNSUPPORTED_CREATIVE_TYPE\" | \"VARIANT_IDENTITY_CONFLICT\" | \"ad_interval_out_of_range\" | \"allocation_out_of_range\" | \"analytics_unavailable\" | \"api_key_expired\" | \"api_key_owner_missing\" | \"api_key_revoked\" | \"array_items_must_be_strings\" | \"array_too_large\" | \"auth_error\" | \"authentication_required\" | \"batch_size_exceeded\" | \"blocked_advertisers_too_large\" | \"blocked_iab_categories_too_large\" | \"client_ip_required\" | \"concurrent_request_limit\" | \"conflict\" | \"conflicting_lists\" | \"creative_policy_conflict\" | \"creative_policy_forbidden\" | \"daily_quota_exceeded\" | \"database_error\" | \"dataset_not_scopable\" | \"device_already_registered\" | \"device_identity_missing\" | \"device_identity_underivable\" | \"device_not_found\" | \"device_offline\" | \"dsp_access_denied\" | \"error\" | \"field_must_be_array\" | \"field_must_be_boolean\" | \"field_too_long\" | \"floor_price_out_of_range\" | \"forbidden\" | \"idempotency_error\" | \"idempotency_key_in_use\" | \"idempotency_key_mismatch\" | \"insufficient_data\" | \"insufficient_permissions\" | \"insufficient_scope\" | \"internal_error\" | \"invalid_ad_surface_mode\" | \"invalid_age_rating\" | \"invalid_api_key\" | \"invalid_api_key_format\" | \"invalid_array_items\" | \"invalid_creative_policy\" | \"invalid_currency\" | \"invalid_dataset\" | \"invalid_device_id\" | \"invalid_dimensions\" | \"invalid_display_mode\" | \"invalid_field_type\" | \"invalid_field_value\" | \"invalid_fingerprint\" | \"invalid_json\" | \"invalid_lbar_position\" | \"invalid_lbar_settings\" | \"invalid_measures\" | \"invalid_media_url\" | \"invalid_order_by\" | \"invalid_orientation\" | \"invalid_pip_position\" | \"invalid_pip_settings\" | \"invalid_preference_level\" | \"invalid_preferences\" | \"invalid_provider\" | \"invalid_screen_id\" | \"invalid_segment_rules\" | \"invalid_sizing_mode\" | \"invalid_stream_type\" | \"invalid_webhook_events\" | \"invalid_webhook_url\" | \"lbar_settings_must_be_object\" | \"lbar_size_out_of_range\" | \"missing_required_field\" | \"not_found\" | \"openrtb_auth_failed\" | \"openrtb_config_missing\" | \"paired_training_insert_failed\" | \"paired_training_rate_limit_exceeded\" | \"partner_inactive\" | \"pip_margin_out_of_range\" | \"pip_settings_must_be_object\" | \"pip_size_ratio_out_of_range\" | \"preferences_must_be_object\" | \"preferences_required\" | \"preset_id_required\" | \"preset_not_found\" | \"rate_limit_exceeded\" | \"resource_locked\" | \"screen_ids_required\" | \"screen_not_found\" | \"screen_not_in_scope\" | \"sdk_enrich_rate_limit_exceeded\" | \"sdk_impression_rate_limit_exceeded\" | \"sdk_qr_beacon_rate_limit_exceeded\" | \"sdk_qr_scan_rate_limit_exceeded\" | \"segment_limit_exceeded\" | \"segment_not_found\" | \"service_unavailable\" | \"stale_data\" | \"stream_content_required\" | \"stream_not_found\" | \"tier_insufficient\" | \"timeout\" | \"timeout_out_of_range\" | \"unknown_agent_type\" | \"unsupported_media_type\" | \"validation_error\" | \"webhook_limit_exceeded\" | \"webhook_not_found\" | \"webhook_secret_expired\" | \"webhook_url_unreachable\";\n\nexport type DataApi_ExampleHealthcheckResponse = {\n /** Echoed X-Request-Id for correlation. */\n request_id?: string;\n service: string;\n /** Always \"ok\" when the service is reachable. */\n status: \"ok\";\n version?: string;\n};\n\nexport type DataApi_FeedbackRewardQuery = {\n /** Opaque next_cursor from this same date scope; rewind after scan_complete. */\n cursor?: string;\n /** Inclusive first play date, YYYY-MM-DD; date range is at most 31 days. */\n from: string;\n /** Unique caller-owned billing key for this page; committed retries return the receipt, not another read. */\n idempotency_key: string;\n /** Maximum native play states returned on this metered page. */\n limit?: number;\n /** Inclusive last play date, YYYY-MM-DD; date range is at most 31 days. */\n to: string;\n};\n\nexport type DataApi_LookalikeResponse = {\n matches: Array<Record<string, unknown>>;\n matches_count: number;\n source_screen_id: string;\n};\n\nexport type DataApi_QueryDslBody = {\n /** Dataset name. Defaults to `inference_outcomes`. */\n dataset?: string;\n dimensions?: Array<string>;\n filters?: Array<DataApi_QueryFilter>;\n limit?: number;\n measures?: Array<string>;\n order_by?: Array<DataApi_QueryOrderBy>;\n};\n\nexport type DataApi_QueryDslResponse = {\n count: number;\n /** `observation_stream` when it is served from ClickHouse: the population the answer counts and the first instant the dataset holds. */\n coverage?: {\n population: string;\n since: string;\n };\n dataset: string;\n dimensions: Array<string>;\n /** `inference_outcomes` only. The `inference_source` values the query read (`null` when a range filter makes the set unstateable), and whether the camera-only default was applied because the request did not filter on `inference_source`. */\n inference_source?: {\n defaulted: boolean;\n values: Array<string> | null;\n };\n limit: number;\n measures: Array<string>;\n /** `inference_outcomes` only. `measured` when every row read is a camera measurement, `modeled` when every row is a venue-profile estimate, `mixed` when the answer aggregates both. */\n provenance?: \"measured\" | \"modeled\" | \"mixed\";\n /** Result rows. For `inference_outcomes` every row also carries `provenance` (`measured`, `modeled` or `mixed`). */\n rows: Array<Record<string, unknown>>;\n};\n\nexport type DataApi_QueryFilter = {\n field: string;\n op?: \"eq\" | \"in\" | \"gte\" | \"lte\" | \"between\";\n value?: unknown;\n};\n\nexport type DataApi_QueryOrderBy = {\n direction?: \"asc\" | \"desc\" | \"ASC\" | \"DESC\";\n field: string;\n};\n\nexport type DataApi_ScreenAudienceResponse = {\n audience: Record<string, unknown> | null;\n message?: string;\n screen_id: string;\n source?: string;\n};\n\nexport type DataApi_ScreenHistoryResponse = {\n count: number;\n metrics: Array<Record<string, unknown>>;\n screen_id: string;\n source?: \"camera\" | \"signal_derived\";\n};\n\nexport type DataApi_ScreensListResponse = {\n count: number;\n has_more: boolean;\n limit: number;\n next_offset: number | null;\n offset: number;\n screens: Array<Record<string, unknown>>;\n total: number;\n};\n\nexport type DataApi_SemanticSearchResponse = {\n query: string;\n results: Array<Record<string, unknown>>;\n results_count: number;\n time_window?: string;\n};\n\nexport type DataApi_StatsResponse = {\n active_screens: number;\n embeddings: Record<string, unknown>;\n metrics_last_24h: number;\n partner?: string;\n};\n\nexport type DataApi_StreamResponse = {\n /** Server-Sent Events stream. Each event carries `data: <json>` lines per the SSE spec. */\n note?: string;\n};\n\nexport type DataApi_UsageResponse = {\n data: Array<Record<string, unknown>>;\n period_days: number;\n};\n\n/**\n * Per-dataset record counts + date ranges for the partner's screens.\n * Returned by `GET /data-export/summary`.\n *\n */\nexport type DataExportSummaryResponse = {\n /** `lookback_days` is `min(?days, 400)` and defaults to 7. `screen_count` is how many screens the export is SCOPED to, not how many hold data. Each `datasets` entry is a `total_records`/`earliest`/`latest` triple, and a dataset whose count query FAILED is reported as `0` with null bounds rather than as an error. */\n data?: {\n /** Map of dataset name to record summary */\n datasets?: Record<string, {\n earliest?: string | null;\n latest?: string | null;\n total_records?: number;\n }>;\n lookback_days?: number;\n screen_count?: number;\n };\n /** Always `true`; a failure is raised to the global error handler and returns a canonical error envelope at a 4xx/5xx status. Note this response carries **no `request_id`**, unlike the sibling audience endpoints. */\n success?: boolean;\n};\n\n/**\n * Canonical INT-PK identity table for demand-side partners (DSPs/SSPs that Trillboards routes ad requests to). Wave 2 of pristine-state refactor. Replaces partner_key TEXT joins across the billing path.\n */\nexport type DemandPartners = {\n archived: boolean;\n created_at: string;\n /** demand_partner_id INT is the canonical FK going forward; partner_key TEXT is kept as the wire-format alternate-key FOREVER (external partner-API auth + admin UI rely on it). Never propose dropping ... */\n demand_partner_id?: string | null;\n display_name: string;\n partner_key: string;\n partner_type: string;\n /** public_alias UNIQUE: this is the obfuscated external alias partner-API consumers see. partnerAlias.js will read from this table after Wave 2 cutover. */\n public_alias: string;\n updated_at: string;\n};\n\nexport type DemographicsResponse = {\n /** `demographics.age_distribution` and `gender_split` are **hardcoded zeros even on the success path** — nothing computes them. `income_profile` and `group_composition` are shares in 0-1, not percentages, and `lifestyle_segments` is emitted as `{segment, percentage}` objects rather than the plain strings declared here. `data_quality.confidence` is a step function of sample size (>=100 gives 0.9, >=50 gives 0.7, >=20 gives 0.5, >=5 gives 0.3, else 0.1), not a statistical confidence, and `sample_size` counts measurement WINDOWS, not people. `period` echoes `?period=`; an unrecognised value silently falls back to 7 days while still being echoed verbatim. */\n data?: {\n data_quality?: {\n confidence?: number;\n sample_size?: number;\n };\n demographics?: {\n age_distribution?: {\n \"18-25\"?: number;\n \"26-35\"?: number;\n \"36-50\"?: number;\n \"50+\"?: number;\n };\n gender_split?: {\n female?: number;\n male?: number;\n };\n group_composition?: {\n couples?: number;\n families?: number;\n friends?: number;\n solo?: number;\n work_groups?: number;\n };\n income_profile?: {\n high?: number;\n low?: number;\n medium?: number;\n premium?: number;\n };\n lifestyle_segments?: Array<string>;\n };\n period?: string;\n screen_id?: string;\n screen_name?: string;\n timestamp?: string;\n };\n /** Per-request identifier (`req_` plus 32 hex), also returned on the `X-Request-Id` response header. */\n request_id?: string;\n /** Always `true` on a 2xx; failures return the error envelope (`success: false` plus an `error` object) at a 4xx/5xx status. */\n success?: boolean;\n};\n\nexport type Device = {\n /** Creation time of the DEVICE row (`earner_screen_devices`), not of the linked screen, which may be created later in the same request or on a subsequent re-registration. */\n created_at?: string;\n /** Trillboards-minted 24-char Mongo ObjectId hex for the `earner_screen_devices` row (`mongo_earnerscreen_device_id`). This is NOT the `device_id` you sent at registration — that comes back as `external_device_id`; the batch endpoint calls this same value `trillboard_id`. All three of `device_id`, `fingerprint` and `external_device_id` are accepted as the `{deviceId}` path parameter, resolved in that order. */\n device_id?: string;\n /** Saved partner category, independent of advertising classification. POST with an existing device_id and PATCH accept category updates. Legacy devices without a saved declaration retain their previous numeric-derived label until explicitly updated. */\n device_type?: string;\n /** Screen display specification. `width`/`height` are pixel resolution; `width_in`/`height_in` are physical inches. Pixel-only inputs are NOT recorded as physical size — pass `width_in`/`height_in` (most accurate) or `ppi` so the server can derive inches from pixels. */\n display?: {\n /** Display resolution height in pixels */\n height?: number;\n /** Physical screen height in inches */\n height_in?: number;\n orientation?: string;\n /** Pixels per inch (required to derive physical size from width/height) */\n ppi?: number;\n /** Display resolution width in pixels */\n width?: number;\n /** Physical screen width in inches */\n width_in?: number;\n };\n /** Static player URL of the form `https://screen.trillboards.com?fp=<fingerprint>`. It is deterministic and permanent — not a signed or expiring link — so it never rotates and anyone holding it can load the player. `null` until a screen is linked. */\n embed_url?: string;\n /** Venue-enrichment state. `status` is `complete` only when a classification source exists AND `location` actually resolved (coordinates or a city), so it never reports complete over placeholder geo; a partner-supplied venue category stamps the source as `partner` at registration, which means `complete` does not imply AI classification ran. `confidence` is dead — `venue_classification_confidence` has no column and no writer anywhere in the API, so it is always undefined and omitted from the JSON. */\n enrichment?: Record<string, unknown>;\n /** Verbatim echo of the `device_id` you sent in the registration body (`earner_screen_devices.partner_external_id`). It is the idempotency key scoped to your partner account: re-POSTing the same value updates the existing device instead of creating a second one. */\n external_device_id?: string;\n /** Deterministic device key: `P_` followed by the first 24 hex chars of `md5(\"<your partner id>_<external_device_id>\")`. It never rotates — the same partner plus the same `device_id` always regenerates it — and it is both the heartbeat/player routing key and the token embedded in `embed_url`. */\n fingerprint?: string;\n /** Saved publisher-declared audience impressions per ad play; defaults to 1. Update via PATCH /v1/partner/device/{deviceId}/impression-multiplier. */\n impression_multiplier?: number;\n /** Resolved geo, read from the enriched screen row first and falling back to the raw registration-time device row, with placeholder strings (\"City\", \"Vending Machine Location\") filtered out. `lat`/`lng` coalesce as a PAIR — a half-set or null-island (0,0) screen pair is discarded whole in favour of the device's coordinates — and `venue_type` here is the OpenOOH CHILD slug, while `venue.category` carries the parent. `timezone` is the one exception to screen-first: it is read device-first. */\n location?: Record<string, unknown>;\n location_capability?: PartnerLocationCapability;\n /** Stripe-style readiness, the same contract registration returns (see https://api.trillboards.com/docs/monetization-requirements). */\n monetization_status?: \"ready\" | \"limited\" | \"blocked\";\n /** The linked screen's name, falling back to the raw `external_device_id` when the screen has none. Omitting `name` at registration seeds the screen as `Partner Device: <device_id>`, so that literal string is what you get back rather than a null. */\n name?: string;\n requirements?: PartnerMonetizationRequirements;\n /** The linked screen's Mongo ObjectId hex (`earner_screens.mongo_earnerscreen_id`), minted server-side at registration. This — not `device_id` — is the id the non-device partner endpoints take (display settings, screen groups, fleet commands, analytics). Absent when no screen is linked to the device. */\n screen_id?: string;\n /** Provisioning state of the underlying screen row, not a liveness signal — a device that has stopped reporting still reads `approved`. Partner registration stamps `approved` on create (`partnerDeviceRegistrationService.js`), which is what almost every live partner device returns; `pending` is the formatter's fallback when the screen row carries no status of its own. */\n status?: \"pending\" | \"approved\" | \"active\" | \"offline\" | \"suspended\";\n /** `earner_screen_devices.updated_at`, which the device-mirror write buffer stamps `NOW()` on every flush — including heartbeat telemetry. It therefore tracks device liveness, not configuration changes; do not poll it to detect that a registration or PATCH landed. */\n updated_at?: string;\n /** OpenOOH classification of the screen, rebuilt from the typed `screen_venue_taxonomy` row. `category` is the PARENT slug and `subcategory` the child, with `openooh_venue_type_id` the unambiguous numeric child id — so a granular input like `hotel` reads back as a parent/child pair, not as what you sent. Empty until classification lands, unless your registration itself carried a resolvable `venue.category`/`subcategory`. */\n venue?: Record<string, unknown>;\n};\n\n/**\n * The device-authentication layer's refusal (missing, invalid or replayed device credential).\n */\nexport type DeviceAuthRefusal = {\n /** The HTTP status. */\n code?: number;\n /** device_token_required, invalid_device_token, device_not_found, device_identity_mismatch, device_token_subject_mismatch, device_screen_mismatch, missing_device_timestamp, missing_device_nonce, stale_device_request, replayed_device_request or replay_guard_unavailable. */\n error?: string;\n message?: string;\n status?: false;\n};\n\nexport type DeviceCapabilityPayload = {\n /** Runtime family of the agent (`fire-tv`, `android-tv`, `tablet`, `tablet-lite`, `webos`, `vidaa`, `web-browser`, `brightsign`). It is NOT read from here on the partner path — the server derives `agent_type` from `telemetry.agentType` plus hardware signals, and overrides a self-declared `web-browser` when the model/UA say otherwise, because partner SDKs running in a WebView historically reported `web-browser` for physical DOOH tablets. */\n agentType?: string | null;\n /** Agent build version. Do not use it to identify an artifact: three different Android build constants populate this one field across three unrelated numbering schemes, so the same string can mean different code. It is also not read from here on the partner path — the version the server keeps comes from the body `agent_version`/`sdk_version`, and only for heartbeats that self-identify as Trillboards SDKs. */\n agentVersion?: string | null;\n /** Camera/microphone sensing state. On the partner path ONLY the nested `cameraHealth` block is persisted (to the typed `camera_health_*` columns) — `cameraAvailable`, `cameraType`, `cameraCount`, `microphoneAvailable` and `sensingMode` reach no column on this path. The presence of `cameraHealth` is also an in-band signal that the heartbeat came from a Trillboards SDK, which is what allows `agent_version` to be stored. `cameraAvailable` self-devolves to false after a camera has been failed for over 24 h. */\n audienceSensing?: {\n cameraAvailable?: boolean | null;\n cameraCount?: number | null;\n cameraHealth?: {\n lastFailureClass?: string | null;\n lastFailureMessage?: string | null;\n lastFrameAtMs?: number | null;\n retryCount?: number | null;\n state?: string | null;\n [key: string]: unknown;\n } | null;\n cameraType?: string | null;\n microphoneAvailable?: boolean | null;\n sensingMode?: string | null;\n [key: string]: unknown;\n } | null;\n /** Declares volume and mute control. Hardcoded `true`/`true` on the Android agents — it is not probed against `AudioManager`, so it is a claim about the agent, not proof the hardware obeys. Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power. */\n audioControl?: {\n mute?: boolean | null;\n volume?: boolean | null;\n [key: string]: unknown;\n } | null;\n /** Schema version of this capability block, persisted to `earner_screen_devices.capabilities_version` for drift attribution. Every shipped Android and Vega agent emits the constant `1` and nothing overrides it, so it carries no information today — but the server treats it as an arbitrary integer, not a bounded enum. Note the server only records it when an `mlCapabilities` block is present or when it is sent with no `mlCapabilities` at all. */\n capabilitiesVersion?: number | null;\n /** Declares an ambient-light and a temperature sensor. Only `ambientLight` is a real probe (`SensorManager.getDefaultSensor(TYPE_LIGHT) != null`, false on any throw); `temperature` is hardcoded `false` on the Android agents, and the actual temperature reading that does ship comes from the battery sensor via telemetry, not from here. Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power. */\n environmentSensors?: {\n ambientLight?: boolean | null;\n temperature?: boolean | null;\n [key: string]: unknown;\n } | null;\n /** Declares HDMI input switching and app switching. Also hardcoded per agent build rather than probed (a tablet reports `hdmi: false`, `appSwitch: true`). Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power. */\n inputControl?: {\n appSwitch?: boolean | null;\n hdmi?: boolean | null;\n [key: string]: unknown;\n } | null;\n /** Whether the agent holds Android Device Owner (`DevicePolicyManager.isDeviceOwnerApp`) — the privilege that gates reboot, kiosk lock, APK install and wipe. Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power, precisely so a partner cannot flip it and unlock those commands. */\n isDeviceOwner?: boolean | null;\n /** Declares remote restart/reboot/screenshot/cache-clear/kiosk/APK-install/wipe. On the first-party Android agents four of the seven are simply `isDeviceOwner` repeated, and the partner SDK reports the whole block `false` because \"partner hosts own their device management\". Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power. */\n managementCapabilities?: {\n clearCache?: boolean | null;\n installApk?: boolean | null;\n kioskMode?: boolean | null;\n reboot?: boolean | null;\n restart?: boolean | null;\n screenshot?: boolean | null;\n wipe?: boolean | null;\n [key: string]: unknown;\n } | null;\n /** The hardware manifest that decides which on-device ML models a screen is offered, and one of the few capability blocks the partner path DOES persist. Two traps: every nullable integer must be sent as JSON `null`, never 0 — the server treats 0 as a probe-failure sentinel and drops it so the COALESCE upsert preserves the prior value — and `screenWidthPx`/`screenHeightPx` below 100 are rejected outright, after agents reporting inches corrupted 940+ rows. A `recommendedModelTier` of `UNKNOWN` (or NULL) excludes the device from on-device demographics entirely. */\n mlCapabilities?: {\n availableRamMb?: number | null;\n chipsetName?: string | null;\n chipsetVendor?: string | null;\n cpuAbi?: string | null;\n densityDpi?: number | null;\n gpuDelegateSupported?: boolean | null;\n gpuName?: string | null;\n hasNpu?: boolean | null;\n maxVlmSizeMb?: number | null;\n nnapiSupported?: boolean | null;\n npuName?: string | null;\n osApiLevel?: number | null;\n recommendedModelTier?: string | null;\n screenHeightPx?: number | null;\n screenWidthPx?: number | null;\n totalRamMb?: number | null;\n [key: string]: unknown;\n } | null;\n /** Declares HDMI-CEC, wake-on-LAN and soft-blackout support. On the Android agents these are HARDCODED per build, not probed (a tablet always reports `cec: false`, `wol: false`, `softBlackout: true`), so they describe the agent variant rather than the panel. Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power. */\n powerControl?: {\n cec?: boolean | null;\n softBlackout?: boolean | null;\n wol?: boolean | null;\n [key: string]: unknown;\n } | null;\n /** Counts ESP32-S3 Wi-Fi CSI sensor nodes discovered on the LAN — it is a statement about the venue's network, not about this device's own radio (`csiHardwareType` is the literal `\"esp32-s3\"` when any node is found, `\"none\"` otherwise). Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power; it is also the one CSI field the ingest schema deliberately keeps, since it is a startup capability counter rather than a streaming data field. */\n wifiCsiSensing?: {\n csiAvailable?: boolean | null;\n csiHardwareType?: string | null;\n csiNodeCount?: number | null;\n [key: string]: unknown;\n } | null;\n [key: string]: unknown;\n};\n\nexport type DeviceSummary = {\n /** Creation time of the DEVICE row, not of the linked screen (which can be created later). */\n created_at?: string;\n /** Trillboards-minted 24-char Mongo ObjectId hex for the `earner_screen_devices` row (`mongo_earnerscreen_device_id`). Not the `device_id` you sent at registration — that is returned as `external_device_id`. Accepted as the `{deviceId}` path parameter, as are `fingerprint` and `external_device_id`. */\n device_id?: string;\n /** Saved mutable partner category, independent of advertising classification. Legacy devices without a saved declaration retain their previous numeric-derived label until explicitly updated. */\n device_type?: string;\n /** Emits `orientation` (from the screen, defaulting to `landscape`) and `ppi` (from the device row). `width`/`height` still read `dimension_width`/`dimension_height`, columns DROPPED from `earner_screen_devices` by migration `20260527180000`, so they resolve to undefined and are omitted from the JSON entirely — registered pixels now live in `screen_width_px`/`screen_height_px`. Physical inches you send as `width_in`/`height_in` are stored but never echoed here. */\n display?: Record<string, unknown>;\n /** Static, unsigned player URL `https://screen.trillboards.com?fp=<fingerprint>`. It never expires or rotates, and is `null` until a screen is linked. */\n embed_url?: string;\n /** Venue-enrichment state. `status` reads `complete` only when a classification source exists AND `location` actually resolved, and a partner-declared venue category alone sets that source — so `complete` does not mean AI classification ran. `confidence` is dead: `venue_classification_confidence` has no column and no writer in the API, so it is always omitted. */\n enrichment?: Record<string, unknown>;\n /** Verbatim echo of the `device_id` you sent in the registration body (`partner_external_id`). It is the per-partner idempotency key: re-registering the same value updates the existing device rather than creating another. */\n external_device_id?: string;\n /** Deterministic device key: `P_` plus the first 24 hex chars of `md5(\"<your partner id>_<external_device_id>\")`. It never rotates, and it is the token embedded in `embed_url` as well as the heartbeat routing key. */\n fingerprint?: string;\n /** Resolved geo: the enriched screen row first, falling back to the registration-time device row, with placeholder strings coalesced away. `lat`/`lng` coalesce as a pair, `venue_type` is the OpenOOH child slug, and `timezone` alone is read device-first. */\n location?: Record<string, unknown>;\n location_capability?: PartnerLocationCapability;\n /** Stripe-style readiness, the same contract registration returns (see https://api.trillboards.com/docs/monetization-requirements). */\n monetization_status?: \"ready\" | \"limited\" | \"blocked\";\n /** The linked screen's name, falling back to the raw `external_device_id` when the screen has none. A registration that omits `name` seeds the screen as `Partner Device: <device_id>`. */\n name?: string;\n requirements?: PartnerMonetizationRequirements;\n /** The linked screen's Mongo ObjectId hex (`earner_screens.mongo_earnerscreen_id`). This is the id every non-device partner endpoint takes — display settings, screen groups, fleet commands, analytics. Absent when no screen is linked. */\n screen_id?: string;\n /** The linked screen's `earner_screens.status` verbatim. Partner registration auto-approves, so the live value is `approved` (`deactivated` after a DELETE, `pending` only while no screen is linked) — not the `pending`/`active`/`offline`/`suspended` vocabulary the `Device` schema declares, whose mapper has no production call site. */\n status?: string;\n /** `earner_screen_devices.updated_at`, stamped `NOW()` by the device-mirror write buffer on every flush including heartbeats — a liveness signal, not a configuration-change marker. */\n updated_at?: string;\n /** OpenOOH classification rebuilt from the typed `screen_venue_taxonomy` row: `category` is the PARENT slug, `subcategory` the child, `openooh_venue_type_id` the numeric child id. Empty until classification lands, unless registration supplied a resolvable venue category. */\n venue?: Record<string, unknown>;\n};\n\nexport type DisplaySettingsInput = {\n /** Default ad duration in seconds */\n ad_duration_default?: number;\n /** Screen brightness percentage */\n brightness?: number;\n content_fit?: ContentFitSettings;\n /** Ad display mode */\n display_mode?: \"fullscreen\" | \"l-bar\" | \"split\";\n /** What to show when no ads */\n idle_content?: \"default_stream\" | \"black\" | \"logo\";\n /** Mute audio */\n mute?: boolean;\n /** Screen orientation */\n orientation?: \"landscape\" | \"portrait\" | \"auto\";\n /** QR code position on screen */\n qr_code_position?: \"bottom-left\" | \"bottom-right\" | \"top-left\" | \"top-right\" | \"none\";\n /** Transition effect between ads */\n transition_effect?: \"fade\" | \"slide\" | \"none\";\n /** Audio volume percentage */\n volume?: number;\n};\n\nexport type DisplaySettingsResponse = {\n /** NOT EMITTED and NOT WRITABLE — the identifier does not occur anywhere in the API source. The nearest real controls are `ad_interval_seconds` on this resource (rotation cadence, 30-300s) and the slot duration the playback policy derives (clamped 5-60s). */\n ad_duration_default?: number;\n /** NOT EMITTED by this endpoint. `buildAdSurfaces` is computed on a display-settings write, but only to populate the `display_settings_updated` socket event pushed to the device — the HTTP body is the formatter's output and contains no `ad_surfaces`. The declared item shape is wrong too: real surfaces carry `id`, `surface_type`, `enabled`, `display_area_ratio`, `dimensions` and `allowed_ad_types`, never `position`/`width_percent`/`height_percent`. The L-bar PUT is the one HTTP response that does return them. */\n ad_surfaces?: Array<{\n height_percent?: number;\n id?: string;\n position?: string;\n width_percent?: number;\n }>;\n /** NOT EMITTED and NOT WRITABLE. The writer's ten-key allow-list has no `brightness`, so a PUT silently drops it, and the formatter never returns one. Brightness exists in this product only as inbound device telemetry on the heartbeat, which this resource cannot set. */\n brightness?: number;\n content_fit?: ContentFitSettings;\n /** Real and emitted: `fullscreen` (default), `l-bar` or `pip`, read from the typed `display_mode` column in preference to the legacy `display_preferences` JSONB. Note the writer validates a THREE-value enum including `pip`, while the L-bar resource's own `display_mode` accepts only `fullscreen`/`l-bar`. */\n display_mode?: string;\n /** NOT EMITTED and NOT WRITABLE — the identifier does not occur anywhere in the API source. What actually plays when no ad is available is the screen's default streams, managed through the default-streams routes. */\n idle_content?: string;\n /** NOT EMITTED and NOT WRITABLE. Use `sound_enabled: false` instead — that key the formatter does return, defaulting to `true`. */\n mute?: boolean;\n /** Always `display_settings`. The response is FLAT — no `success`/`data` envelope — and it comes from exactly one formatter shared by the GET, PUT and PATCH, so all three return an identical shape. */\n object?: string;\n /** NOT EMITTED under this name — the formatter returns `screen_orientation` instead (`landscape`, `portrait`, `mixed` or `unknown`, defaulting to `landscape`). The writer's allow-list also only accepts `screen_orientation`, so an `orientation` key in a PUT body is silently dropped. */\n orientation?: string;\n /** NOT EMITTED by this endpoint, for the same reason as `ad_surfaces` — it is computed on a write only to fill the device socket event. The declared `max_duration_seconds`/`min_break_between_ads`/`allow_skip` keys do not exist in the real policy either; `buildPlaybackPolicy` produces `policy_version`, millisecond intervals, `max_paid_share`, `surface_policy`, `schedule_policy`, `pip_settings` and `lbar_settings`. */\n playback_policy?: {\n allow_skip?: boolean;\n max_duration_seconds?: number;\n min_break_between_ads?: number;\n };\n /** NOT EMITTED and NOT WRITABLE — the identifier does not occur anywhere in the API source. */\n qr_code_position?: string;\n /** Echo of the server-assigned `req.requestId`, attached only when one exists — the key is absent, not null, otherwise. */\n request_id?: string;\n /** Echoed from the `{screenId}` path parameter rather than read from a column. */\n screen_id?: string;\n /** NOT EMITTED and NOT WRITABLE — the identifier `transition_effect` does not occur anywhere in the API source. There is no transition control on this resource. */\n transition_effect?: string;\n /** ISO-8601 of the SCREEN row's `updatedAt`, not a display-settings-specific timestamp; when the screen has none the formatter substitutes the current time, so it is never null and never proves a write landed. */\n updated_at?: string;\n /** NOT EMITTED and NOT WRITABLE — the string `volume` does not appear in the display-settings service or the formatter at all. The only audio control on this resource is the boolean `sound_enabled`. */\n volume?: number;\n};\n\n/**\n * User-owned ad screens. Each row is a logical screen that can display ads. Links user to device. PG primary with Mongo legacy mirror.\n */\nexport type EarnerScreens = {\n ad_interval_seconds?: number | null;\n ads_disabled_reasons: Array<string>;\n advertisement_sizing?: string | null;\n archetype_photo_key?: string | null;\n archetype_photo_synced_at?: string | null;\n archetype_photo_url?: string | null;\n auction_cohort?: string | null;\n auction_enabled_at?: string | null;\n auction_mode?: string | null;\n auction_type?: string | null;\n avg_dwell_time_seconds?: number | null;\n back_online_alert_sent_at?: string | null;\n billing_mode: string;\n block_group_geoid?: string | null;\n blocked_advertisers?: Array<string> | null;\n blocked_iab_categories?: Array<string> | null;\n brand_substitution_consent_at?: string | null;\n brand_substitution_consent_by?: string | null;\n brand_substitution_mode: string;\n branding_overlay_enabled: boolean;\n branding_overlay_hide_on_ads: boolean;\n branding_overlay_logo_transformed_url: string;\n branding_overlay_logo_url: string;\n /** NUMERIC(5,4). */\n branding_overlay_opacity: number;\n /** NUMERIC(5,4). */\n branding_overlay_padding_ratio: number;\n branding_overlay_placement: string;\n branding_overlay_position: string;\n /** NUMERIC(5,4). */\n branding_overlay_size_ratio: number;\n cms_payment_status: string;\n commercial_value_score?: number | null;\n content_filtering_enabled?: boolean | null;\n /** deleted_at IS NULL required — soft-delete nulls earnerscreen_device_id for partial unique index */\n deleted_at?: string | null;\n delivery_mode: string;\n device_platform_override?: string | null;\n diagonal_inches?: number | null;\n display_mode?: string | null;\n distributor_id?: number | null;\n dma_code?: string | null;\n dma_name?: string | null;\n dynamic_floor_pricing?: boolean | null;\n enrichment_attempts: number;\n enrichment_failed_at?: string | null;\n environment_type?: string | null;\n /** NUMERIC(12,6). */\n floor_price_cpm?: number | null;\n foot_traffic_multiplier?: number | null;\n gam_ad_unit_code?: string | null;\n gam_ad_unit_id?: string | null;\n gam_ad_unit_path?: string | null;\n gam_sync_status?: string | null;\n gam_synced_at?: string | null;\n geocode_source?: string | null;\n geocoded_at?: string | null;\n geocoding_raw_response?: Record<string, unknown> | null;\n hb_allocation?: number | null;\n hb_enabled?: boolean | null;\n hb_force_request?: boolean | null;\n hb_last_sync_at?: string | null;\n hb_min_interval_seconds?: number | null;\n hb_provider?: string | null;\n hb_request_mode?: string | null;\n hb_timeout_ms?: number | null;\n hb_vast_tag_url?: string | null;\n height_inches?: number | null;\n hive_stack_screen_id?: string | null;\n hive_stack_site_id?: string | null;\n hive_stack_sync_status?: string | null;\n hive_stack_synced_at?: string | null;\n hive_stack_unit_id?: string | null;\n hive_stack_unit_uuid?: string | null;\n is_demo_data: boolean;\n is_standby: boolean;\n l_bar_ads_enabled?: boolean | null;\n l_bar_position?: string | null;\n last_enrichment_attempt_at?: string | null;\n last_offline_transition_at?: string | null;\n last_programmatic_sync_at?: string | null;\n latitude?: number | null;\n longitude?: number | null;\n loop_plan_id?: number | null;\n media_capability?: \"image_video\" | \"image_only\" | \"video_only\" | null;\n mediation_priority?: number | null;\n mobile_distance_threshold_m?: number | null;\n mobile_update_interval_s?: number | null;\n mobile_vehicle_type?: string | null;\n neighborhood?: string | null;\n ntp_sync_required: boolean;\n offline_alert_sent_at?: string | null;\n offline_push_alert_sent_at?: string | null;\n operating_hours?: Record<string, unknown> | null;\n partner_mongo_id?: string | null;\n peak_hours?: Array<string> | null;\n photo_flag_notes?: string | null;\n photo_flag_reason?: string | null;\n photo_flagged: boolean;\n photo_flagged_at?: string | null;\n photo_flagged_by_mongo_id?: string | null;\n photo_notification_count: number;\n photo_notification_sent_at?: string | null;\n photo_quality_sent_at?: string | null;\n photo_review_status?: string | null;\n photos?: Record<string, unknown> | null;\n physical_size_set_at?: string | null;\n physical_size_set_by?: string | null;\n place_name?: string | null;\n placement_type?: string | null;\n places_rating?: number | null;\n places_review_count?: number | null;\n pref_allow_religious?: boolean | null;\n pref_allow_tobacco?: boolean | null;\n pref_allowed_categories?: Array<string> | null;\n pref_allowed_keywords?: Array<string> | null;\n pref_allowed_subjects?: Array<string> | null;\n pref_auto_approve_known_brands?: boolean | null;\n pref_blocked_categories?: Array<string> | null;\n pref_blocked_keywords?: Array<string> | null;\n pref_blocked_subjects?: Array<string> | null;\n pref_level?: string | null;\n pref_max_age_rating?: string | null;\n preferred_deal_ids: Array<string>;\n prog_allocation_pct?: number | null;\n prog_currency: string;\n programmatic_enabled: boolean;\n region: string;\n registration_source?: string | null;\n screen_placement?: string | null;\n serving_tenant_id?: number | null;\n slot_duration_seconds?: number | null;\n sound_enabled?: boolean | null;\n ssp_enabled?: boolean | null;\n state_iso_3166_2?: string | null;\n sync_enabled?: boolean | null;\n sync_group_name?: string | null;\n sync_is_master?: boolean | null;\n sync_last_sync_at?: string | null;\n sync_master_screen_id?: string | null;\n taggify_screen_id?: string | null;\n taxonomy_migrated_at?: string | null;\n taxonomy_spec_version?: string | null;\n venue_category?: string | null;\n venue_child?: string | null;\n venue_child_id?: string | null;\n venue_city?: string | null;\n /** enrichment_status: PG GENERATED from (venue_classification_source, enrichment_failed_at); a real classification DOMINATES a stale failure stamp (success wins). Never written directly. Values: pendi... */\n venue_classification_source?: string | null;\n venue_grandchild?: string | null;\n venue_parent?: string | null;\n venue_state?: string | null;\n venue_subcategory?: string | null;\n waterfall_optimized_at?: string | null;\n /** waterfall_partners is the ONLY pin store (order + whitelist; screen_partner_settings was never built). Serving reads it only with a screen_header_bidding_config row and ignores it once waterfall_op... */\n waterfall_partners: Array<string>;\n waterfall_total_timeout_ms?: number | null;\n /** Physical width_inches/height_inches/diagonal_inches are inches. Pixels live on earner_screen_devices; the raw screen has no resolution_width/resolution_height columns. */\n width_inches?: number | null;\n};\n\nexport type EarningsBreakdownResponse = {\n /** Keyed by `date` for `group_by=day|week` and by `screen_id` (plus a `screen_name`) for `group_by=screen`; the unused key is absent rather than null. Week rows are labelled `<year>-W<ISO week>`. `earnings_cents` is allocated cumulatively so the rows sum exactly to `summary.total_cents`. */\n breakdown?: Array<{\n /** Date or screen_id depending on group_by */\n date?: string;\n earning_sources?: EarningsSources;\n earnings_cents?: number;\n impressions?: number;\n /** Screen ID (when grouped by screen) */\n screen_id?: string;\n }>;\n /** **Both fields are hardcoded** — `estimated_cents` is always `0` and `confirmation_date` always `null`. Read `EarningsResponse.pending_confirmation_cents` for the real Net-60 pending figure. */\n pending_programmatic?: {\n confirmation_date?: string | null;\n estimated_cents?: number;\n };\n /** `start`/`end` echo the request; with neither supplied the window is the last 30 days ending now. The end bound is EXCLUSIVE in the underlying query (`period_date < end`), so passing today's date omits today. */\n period?: {\n end?: string;\n group_by?: \"day\" | \"week\" | \"screen\";\n start?: string;\n };\n /** Integer US cents for the selected period. With source reporting enabled, direct_cents is verified first-party revenue and programmatic_cents is the remainder; earning_sources supplies the business classification. Disabled reporting retains the legacy split. */\n summary?: {\n direct_cents?: number;\n earning_source_version?: 1;\n earning_sources?: EarningsSources;\n programmatic_cents?: number;\n total_cents?: number;\n total_impressions?: number;\n };\n};\n\n/**\n * Partner earnings summary with optional daily series\n * (`@trillboards/api-client` `EarningsData`).\n *\n */\nexport type EarningsData = {\n /** **Never emitted.** No partner earnings endpoint computes a CPM; the earnings chart returns impressions and cents only, so a caller must divide for itself. */\n average_cpm: number;\n /** Optional per-day earnings/impressions series. */\n daily?: Array<{\n /** Day label (YYYY-MM-DD). */\n date: string;\n earnings: number;\n impressions: number;\n }>;\n /** **Never emitted**, so its unit is undefined. Every money field the partner earnings endpoints actually return is integer US cents and carries a `_cents` suffix. */\n last_payout_amount?: number;\n /** **Never emitted.** No handler populates it — read `GET /v1/partner/earnings/transactions?type=payout`, whose rows carry `created_at` and `amount_cents`. */\n last_payout_date?: string;\n /** Balance not yet paid out. */\n pending_payout: number;\n /** Declared for the SDK type but **not emitted**: `GET /v1/partner/connect/earnings` returns `{summary, chart, period}` where `period` is an OBJECT (`days`/`start`/`end`), not this string. */\n period: string;\n /** **Not emitted under this name**, so its unit is undefined. The live earnings chart returns `summary.total_earnings_cents` — integer US cents — summed over the requested window from the payout-accounting rollup of `programmatic_earnings_daily`. */\n total_earnings: number;\n /** **Not emitted under this name.** The live earnings chart returns `summary.total_impressions`, the impression count carried on the same daily earner-revenue rows the cents are summed from. */\n total_impressions: number;\n};\n\nexport type EarningsResponse = {\n /** Amount available for immediate payout */\n available_for_payout_cents?: number;\n /** Current-month integer US cents. With source reporting enabled, direct_campaigns is verified first-party revenue and programmatic_google is the remaining aggregate, not Google-specific. programmatic_floodgates remains zero. Prefer earning_sources; disabled reporting retains the legacy all-programmatic split. */\n breakdown?: {\n direct_campaigns?: number;\n programmatic_floodgates?: number;\n programmatic_google?: number;\n };\n earning_source_version?: 1;\n earning_sources?: EarningsSources;\n /** `amount_cents` is integer US cents and simply repeats `available_for_payout_cents`; `date` is the 1st of next month, computed in the handler with no reference to any scheduled transfer; `stripe_status` is the partner's Connect account status, which must be `active` before a payout request is accepted. */\n next_payout_estimate?: {\n amount_cents?: number;\n date?: string;\n stripe_status?: string;\n };\n /** Minimum amount required for payout */\n payout_threshold_cents?: number;\n /** Programmatic earnings pending Net-60 confirmation */\n pending_confirmation_cents?: number;\n /** Partner's revenue share percentage */\n revenue_share_percent?: number;\n /** Estimated earnings for current month */\n this_month_cents?: number;\n /** Total earnings since partner started */\n total_lifetime_cents?: number;\n};\n\n/**\n * Optional reporting breakdown in integer US cents. Components sum to the existing amount. Missing evidence is Unclassified; absence of this object means reporting is unavailable.\n */\nexport type EarningsSources = {\n /** Paid first-party portal and offline IO bookings. */\n direct_bookings: number;\n /** Verified open-auction delivery outside a private deal. */\n open_exchange: number;\n /** Verified delivered DSP/SSP PMP, PG and preferred deals. */\n programmatic_deals: number;\n /** Earnings lacking supported allocation evidence. */\n unclassified: number;\n};\n\n/**\n * Legacy free-text error shape. COLLAPSED onto the shared `LegacyError`\n * definition so there is exactly one declaration of this shape across the\n * specs. Kept as a distinct NAME because hand-authored operations\n * reference it by that name; the shape is unchanged.\n *\n */\nexport type Error = LegacyError;\n\n/**\n * The 143 CATALOGUED error codes, generated from utils/errorCatalog.js — the same catalog that backs https://api.trillboards.com/docs/errors. This is the documented registry, NOT the complete set of codes the API can emit: a response `code` is an open string (see `TrillboardsError`). Use this list to recognise a code, never to assume a response cannot carry another.\n */\nexport type ErrorCode = \"AUTH_REQUIRED\" | \"BRAND_CONFIGURATION_FAILED\" | \"BRAND_PROFILE_CONFLICT\" | \"BRAND_PROFILE_NOT_FOUND\" | \"CREATIVE_ADVERTISEMENT_NOT_OWNED\" | \"CREATIVE_MODERATION_UNAVAILABLE\" | \"CREATIVE_NOT_LINKED\" | \"CREATIVE_NOT_READY\" | \"CREATIVE_POLICY_CONFLICT\" | \"CREATIVE_TOO_LARGE\" | \"EXPERIMENT_COMPLETED\" | \"EXPERIMENT_NOT_FOUND\" | \"FEEDBACK_POLICY_REQUIRED\" | \"IDEMPOTENCY_CONFLICT\" | \"INVALID_BRAND_RULES\" | \"INVALID_CREATIVE_URL\" | \"INVALID_CREATIVE_WEIGHTS\" | \"INVALID_FEEDBACK_CREATIVE\" | \"INVALID_FEEDBACK_OBJECTIVE\" | \"INVALID_FEEDBACK_REQUEST\" | \"INVALID_POLICY_VERSION\" | \"INVALID_VARIANTS\" | \"MEDIA_BUY_NOT_FOUND\" | \"OPERATION_ALREADY_COMMITTED\" | \"OPERATION_IN_PROGRESS\" | \"POLICY_NOT_AVAILABLE\" | \"SOURCE_CREATIVE_NOT_FOUND\" | \"UNSUPPORTED_CREATIVE_TYPE\" | \"VARIANT_IDENTITY_CONFLICT\" | \"ad_interval_out_of_range\" | \"allocation_out_of_range\" | \"analytics_unavailable\" | \"api_key_expired\" | \"api_key_owner_missing\" | \"api_key_revoked\" | \"array_items_must_be_strings\" | \"array_too_large\" | \"auth_error\" | \"authentication_required\" | \"batch_size_exceeded\" | \"blocked_advertisers_too_large\" | \"blocked_iab_categories_too_large\" | \"client_ip_required\" | \"concurrent_request_limit\" | \"conflict\" | \"conflicting_lists\" | \"creative_policy_conflict\" | \"creative_policy_forbidden\" | \"daily_quota_exceeded\" | \"database_error\" | \"dataset_not_scopable\" | \"device_already_registered\" | \"device_identity_missing\" | \"device_identity_underivable\" | \"device_not_found\" | \"device_offline\" | \"dsp_access_denied\" | \"error\" | \"field_must_be_array\" | \"field_must_be_boolean\" | \"field_too_long\" | \"floor_price_out_of_range\" | \"forbidden\" | \"idempotency_error\" | \"idempotency_key_in_use\" | \"idempotency_key_mismatch\" | \"insufficient_data\" | \"insufficient_permissions\" | \"insufficient_scope\" | \"internal_error\" | \"invalid_ad_surface_mode\" | \"invalid_age_rating\" | \"invalid_api_key\" | \"invalid_api_key_format\" | \"invalid_array_items\" | \"invalid_creative_policy\" | \"invalid_currency\" | \"invalid_dataset\" | \"invalid_device_id\" | \"invalid_dimensions\" | \"invalid_display_mode\" | \"invalid_field_type\" | \"invalid_field_value\" | \"invalid_fingerprint\" | \"invalid_json\" | \"invalid_lbar_position\" | \"invalid_lbar_settings\" | \"invalid_measures\" | \"invalid_media_url\" | \"invalid_order_by\" | \"invalid_orientation\" | \"invalid_pip_position\" | \"invalid_pip_settings\" | \"invalid_preference_level\" | \"invalid_preferences\" | \"invalid_provider\" | \"invalid_screen_id\" | \"invalid_segment_rules\" | \"invalid_sizing_mode\" | \"invalid_stream_type\" | \"invalid_webhook_events\" | \"invalid_webhook_url\" | \"lbar_settings_must_be_object\" | \"lbar_size_out_of_range\" | \"missing_required_field\" | \"not_found\" | \"openrtb_auth_failed\" | \"openrtb_config_missing\" | \"paired_training_insert_failed\" | \"paired_training_rate_limit_exceeded\" | \"partner_inactive\" | \"pip_margin_out_of_range\" | \"pip_settings_must_be_object\" | \"pip_size_ratio_out_of_range\" | \"preferences_must_be_object\" | \"preferences_required\" | \"preset_id_required\" | \"preset_not_found\" | \"rate_limit_exceeded\" | \"resource_locked\" | \"screen_ids_required\" | \"screen_not_found\" | \"screen_not_in_scope\" | \"sdk_enrich_rate_limit_exceeded\" | \"sdk_impression_rate_limit_exceeded\" | \"sdk_qr_beacon_rate_limit_exceeded\" | \"sdk_qr_scan_rate_limit_exceeded\" | \"segment_limit_exceeded\" | \"segment_not_found\" | \"service_unavailable\" | \"stale_data\" | \"stream_content_required\" | \"stream_not_found\" | \"tier_insufficient\" | \"timeout\" | \"timeout_out_of_range\" | \"unknown_agent_type\" | \"unsupported_media_type\" | \"validation_error\" | \"webhook_limit_exceeded\" | \"webhook_not_found\" | \"webhook_secret_expired\" | \"webhook_url_unreachable\";\n\nexport type ErrorCodeResponse = {\n code: string;\n description: string;\n doc_url: string;\n /** Example canonical error envelope for this code. */\n example_response: {\n error: {\n code: string;\n doc_url: string;\n message: string;\n param?: unknown;\n type: string;\n };\n };\n http_status: number;\n /** Always \"error_code\". */\n object: \"error_code\";\n type: string;\n};\n\n/**\n * A single partner API error-log record\n * (`@trillboards/api-client` `ErrorEntry`) — one logged error occurrence\n * scoped to the partner. Distinct from the public error *catalog* entries\n * (`ErrorListItem` / `ErrorCodeResponse`), which describe error codes in\n * the abstract.\n *\n */\nexport type ErrorEntry = {\n /** Stable machine error code (e.g. not_found). */\n code: string;\n /** Derived mechanically as `https://api.trillboards.com/docs/errors#<code>`, so it is always well-formed whether or not that anchor exists. It appears on live error envelopes and catalog entries; no per-partner error-log endpoint returns it. */\n doc_url: string;\n /** The API path that produced the error. */\n endpoint: string;\n /** **No producer.** `GET /v1/errors` returns the static error CATALOG (code definitions in the abstract), not per-partner occurrences, and the per-request metadata the API does retain is keyed by `request_id` and carries no `id`. */\n id: string;\n /** **Never emitted as part of a logged occurrence.** Human-readable text appears only on a live error envelope or a catalog entry; the retained per-request record stores `error_code`/`error_type` and no message. */\n message: string;\n /** The HTTP method of the failing request. */\n method: string;\n /** The request parameter the error concerns, if any. */\n param?: string;\n /** The `req_`-prefixed identifier of the failing request, minted server-side and returned both on the `X-Request-Id` header and inside every error envelope. It is the only handle that links a caller-side failure to server-side request metadata. */\n request_id: string;\n /** **Not emitted by any error endpoint.** The per-request metadata the API retains does stamp an ISO timestamp at response time, but it lives in a bounded in-memory LRU cache, not a queryable error log. */\n timestamp: string;\n /** Stripe-style error taxonomy (matches the SDK's ApiErrorType). */\n type: \"invalid_request_error\" | \"authentication_error\" | \"permission_error\" | \"rate_limit_error\" | \"idempotency_error\" | \"api_error\";\n};\n\n/**\n * The complete set of error envelopes this API may return. Documented on operations whose surface emits more than one — see `x-superseded-by` on each member for the migration target. New endpoints return `TrillboardsError`; switch on `error.code`, never on a message string.\n */\nexport type ErrorEnvelope = TrillboardsError | HybridError | LegacyCodedError | LegacyError;\n\nexport type ErrorListItem = {\n code: string;\n description: string;\n doc_url: string;\n http_status: number;\n type: string;\n};\n\nexport type ErrorListResponse = {\n data: Array<ErrorListItem>;\n /** Always \"list\". */\n object: \"list\";\n /** Total number of error codes. */\n total: number;\n};\n\n/**\n * Stripe-style error taxonomy. Derived from the distinct `type` values in utils/errorCatalog.js (6 values).\n */\nexport type ErrorType = \"api_error\" | \"authentication_error\" | \"idempotency_error\" | \"invalid_request_error\" | \"permission_error\" | \"rate_limit_error\";\n\n/**\n * Exact DSP-matchable audience identity kept separate from the resettable\n * OS advertising ID. `hem_sha256` is an existing SHA-256 digest: send it\n * directly (the server lowercases hexadecimal once and never hashes it\n * again). `uid2_token` is an opaque, case-sensitive UID2 advertising\n * token: send it exactly as issued and include `expires_at` so it can be\n * refreshed; tokens contain no whitespace. Use `unavailable` with no\n * `id` to clear a prior value.\n *\n */\nexport type ExtendedIdentity = {\n /** OpenRTB person-based identifier type. */\n atype: 3;\n /** When the token stops being usable — for a UID2 token this is the refresh deadline, and it is stored verbatim regardless of `status` (unlike the advertising-identity envelope, which discards `expires_at` on an `available` state). `null` means no declared expiry, not \"already expired\". */\n expires_at?: string | null;\n /** The identity value itself, up to 4096 characters and containing no whitespace. Omit it (or send `null`) with `unavailable` to CLEAR a previously stored identity — that is the only way to retract one; sending the envelope with `available` and an empty id is rejected instead. */\n id?: string | null;\n /** `hem_sha256` must be exactly 64 hexadecimal characters and is lowercased ONCE server-side and never re-hashed — send the digest, not the email. `uid2_token` is an opaque case-sensitive token forwarded byte-for-byte with only a whitespace check, so do not trim, lowercase or re-encode it. Together with `source` this pair is the row key: one identity per `(id_type, source)` per device. */\n id_type: \"hem_sha256\" | \"uid2_token\";\n /** When the device observed this identity — and it is the CONFLICT-RESOLUTION CLOCK, not just provenance: the upsert applies only when this timestamp is greater than or equal to the one already stored, so a replayed or out-of-order envelope is silently discarded with no error. An unparseable value rejects the whole array. */\n observed_at: string;\n /** Canonical top-level domain for this identity system. */\n source: string;\n /** Version of the SDK or integration that produced this identity, up to 128 characters, kept for provenance only — no server logic branches on it. It is trimmed, and a whitespace-only string is stored as `null` rather than as an empty string. */\n source_version?: string | null;\n /** `available` requires `id`; `unavailable` requires `id` to be ABSENT — sending both is a validation failure, and because normalisation is all-or-nothing across the array that single bad element discards every other identity in the same request while the heartbeat still returns 200. */\n status: \"available\" | \"unavailable\";\n};\n\nexport type FeedbackBrand = {\n advertisement_id: string;\n brand_name: string;\n profile_id: string;\n reactive_enabled: true;\n stance: \"adversarial\" | \"complementary\" | \"same_brand\" | \"intent_capture\";\n};\n\nexport type FeedbackBrandRequest = {\n /** Native brand reference assets used by creative generation. */\n brand_assets?: Array<Record<string, unknown>>;\n /** Optional brand intent supplied to the native creative generator. */\n brand_intent_text?: string | null;\n /** Advertiser brand name for the experiment’s native campaign. */\n brand_name: string;\n /** Native brand colours, typography, voice, products and required or prohibited elements. */\n brand_rules: {\n /** The advertiser brand name, e.g. \"Nike\". */\n brand_name: string;\n /** Elements that MUST appear in every creative, e.g. [\"logo top-right\",\"legal disclaimer\"]; may be empty. */\n mandatory_elements: Array<string>;\n /** Brand colour palette. */\n palette: {\n /** Accent colours; may be empty. */\n accents: Array<string>;\n /** Primary brand colour (hex or named); empty string if unknown. */\n primary: string;\n /** Secondary brand colour; empty string if none. */\n secondary: string;\n };\n /** Key products to feature; may be empty. */\n products: Array<{\n /** Short product description; empty string if none. */\n description: string;\n /** Product name. */\n name: string;\n }>;\n /** Elements that must NEVER appear, e.g. [\"competitor logos\",\"alcohol\"]; may be empty. */\n prohibited_elements: Array<string>;\n /** Preferred reactive stances for this brand; may be empty. */\n stance_preferences: Array<\"adversarial\" | \"complementary\" | \"same_brand\" | \"intent_capture\">;\n /** Approved taglines / slogans; may be empty. */\n taglines: Array<string>;\n /** Typography guidance. */\n typography: {\n /** Body typeface / style guidance; empty string if none. */\n body: string;\n /** Headline typeface / style guidance; empty string if none. */\n headline: string;\n };\n /** Voice + tone adjectives, e.g. [\"confident\",\"playful\"]; may be empty. */\n voice_tone: Array<string>;\n };\n /** Optional existing brand profile owned by this advertiser and advertisement. */\n profile_id?: string;\n /** Native reactive relationship used to generate or stage this creative. */\n stance?: \"adversarial\" | \"complementary\" | \"same_brand\" | \"intent_capture\";\n};\n\nexport type FeedbackBrandResponse = {\n data: FeedbackBrand;\n success: true;\n};\n\nexport type FeedbackCreateRequest = {\n /** Exact decimal policy version; zero enrolls an unclaimed native policy. */\n expected_policy_version?: string;\n /** Caller-owned key for this exact operation; do not reuse for changed inputs. */\n idempotency_key: string;\n /** Your funded native AdCP media buy ID. */\n media_buy_id: string;\n /** Name for this funded creative feedback experiment. */\n name: string;\n /** Measured objective: gaze share 0–1, gaze overlap milliseconds, or capped mean dwell milliseconds. */\n objective: \"gaze_share\" | \"gaze_overlap_ms\" | \"dwell_ms\";\n /** Native package ID within that media buy. */\n package_id: string;\n};\n\nexport type FeedbackCreativeReview = {\n success: boolean;\n};\n\nexport type FeedbackExperiment = {\n advertisement_id: string;\n advertiser_id: string;\n id: string;\n media_buy_id: string;\n name: string;\n objective: \"gaze_share\" | \"gaze_overlap_ms\" | \"dwell_ms\";\n package_id: string;\n policy_mode: \"attribution\" | \"experiment\" | \"external_agent\";\n policy_version: string;\n status: string;\n variants: Array<FeedbackVariant>;\n};\n\nexport type FeedbackExperimentDetail = FeedbackExperiment & {\n creatives: Array<FeedbackRecoveredCreative>;\n creatives_truncated: boolean;\n };\n\nexport type FeedbackExperimentDetailResponse = {\n data: FeedbackExperimentDetail;\n success: true;\n};\n\nexport type FeedbackExperimentResponse = {\n data: FeedbackExperiment;\n success: true;\n};\n\nexport type FeedbackGenerateRequest = {\n /** Image aspect ratio, such as 16:9, matched to your booked screens. */\n aspectRatio?: string;\n /** Creative direction added to your configured native brand rules. */\n brief?: string;\n /** Unique caller-owned generation key; committed retries expose creativeId and renderKey in the receipt. */\n idempotency_key: string;\n /** Native reactive relationship used to generate or stage this creative. */\n stance?: \"adversarial\" | \"complementary\" | \"same_brand\" | \"intent_capture\";\n};\n\nexport type FeedbackGenerateResponse = {\n data: FeedbackGeneratedCreative;\n success: true;\n};\n\nexport type FeedbackGeneratedCreative = {\n brandReviewStatus: string;\n cdnUrl: string;\n creativeId: string;\n deduped: boolean;\n headline?: string | null;\n moderationStatus?: string;\n renderKey: string;\n status?: string;\n success: true;\n usage_receipt: Record<string, unknown>;\n};\n\n/**\n * Exactly one native image source is required. source_creative_id resolves only within the authenticated partner. Inline bytes require content_type and are validated against the actual image MIME and byte hash by native staging.\n */\nexport type FeedbackImportRequest = {\n /** Public HTTPS image URL; provide exactly one image source. */\n asset_url?: string;\n /** Actual image MIME; required for inline bytes and checked against decoded content. */\n content_type?: \"image/png\" | \"image/jpeg\" | \"image/jpg\" | \"image/webp\";\n /** Optional headline stored with the native imported creative. */\n headline?: string;\n /** Unique caller-owned import key; a committed retry returns the existing usage receipt. */\n idempotency_key: string;\n /** Base64 PNG, JPEG or WebP bytes; provide exactly one source and content_type. */\n image_base64?: string;\n /** Existing creative ID owned by the authenticated partner; provide exactly one image source. */\n source_creative_id?: string;\n /** Native reactive relationship used to generate or stage this creative. */\n stance?: \"adversarial\" | \"complementary\" | \"same_brand\" | \"intent_capture\";\n};\n\nexport type FeedbackPolicyRequest = {\n /** Exact decimal policy version from the latest experiment; stale updates return 409. */\n expected_version: string;\n /** Replacement weights for registered variants; omitted variants are inactive. */\n weights: Array<{\n /** Registered native creative ID to assign this weight. */\n creative_id: string;\n /** Weight between zero and one; zero disables selection. Eligible weights normalize at serving. */\n weight: number;\n }>;\n};\n\nexport type FeedbackRecoveredCreative = {\n advertisement_mongo_id: string | null;\n brand_profile_id: string | null;\n brand_review_status: string;\n cdn_url: string;\n content_sha256: string | null;\n created_at: string;\n creative_id: string;\n headline: string | null;\n linked_to_experiment_ad: boolean;\n moderation_status: string | null;\n render_key: string | null;\n reviewed_at: string | null;\n stance: string | null;\n};\n\nexport type FeedbackRegisterVariantsRequest = {\n /** Exact decimal policy version from the latest experiment; stale updates return 409. */\n expected_version: string;\n /** Approved native variants; identity and bytes remain immutable. */\n variants: Array<{\n /** Approved native generated or imported creative ID. */\n creative_id: string;\n /** Your model version associated with this immutable variant. */\n model_version?: string;\n /** Stable model variant identity within this experiment. */\n variant_id: string;\n }>;\n};\n\nexport type FeedbackReviewRequest = {\n /** Owned native creative ID awaiting your explicit brand review. */\n creative_id: string;\n /** Approve or reject brand use; global AI moderation remains a separate gate. */\n decision: \"approve\" | \"reject\";\n};\n\nexport type FeedbackReviewResponse = {\n data: FeedbackCreativeReview;\n success: true;\n};\n\nexport type FeedbackRewardQuery = {\n /** Opaque next_cursor from this same date scope; rewind after scan_complete. */\n cursor?: string;\n /** Inclusive first play date, YYYY-MM-DD; date range is at most 31 days. */\n from: string;\n /** Unique caller-owned billing key for this page; committed retries return the receipt, not another read. */\n idempotency_key: string;\n /** Maximum native play states returned on this metered page. */\n limit?: number;\n /** Inclusive last play date, YYYY-MM-DD; date range is at most 31 days. */\n to: string;\n};\n\nexport type FeedbackRewardRow = {\n action: \"upsert\" | \"retract\";\n ad_id: string | null;\n build_stamp: string;\n builder_version: string;\n /** Four ordered strings: play_date, screen_mongo_id, play_source, play_key. */\n canonical_key: Array<string>;\n content_sha256: string;\n /** Lossless issued v1 JSON snapshot; empty string when unissued. Contains variant_id and creative_policy_version. */\n creative_feedback: string;\n creative_id: string | null;\n dwell_capped_ms_mean: number | null;\n dwell_overlap_ms: number | null;\n dwell_windows_n: number | null;\n gaze_face_rows_n: number | null;\n gaze_overlap_ms: number | null;\n /** Measured gaze share, 0–1; NULL when not measured. */\n gaze_pct_on_screen: number | null;\n gaze_seconds: number | null;\n gaze_windows_n: number | null;\n play_completed_at: string | null;\n play_date: string;\n play_key: string;\n play_source: string;\n play_started_at: string | null;\n request_id: string | null;\n reward_revision_id: string;\n screen_mongo_id: string;\n superseded: number;\n};\n\nexport type FeedbackRewards = {\n experiment_id: string;\n next_cursor: string | null;\n objective: \"gaze_share\" | \"gaze_overlap_ms\" | \"dwell_ms\";\n rewind_next_poll: boolean;\n rows: Array<FeedbackRewardRow>;\n scan_complete: boolean;\n usage_receipt: Record<string, unknown>;\n view: \"latest_state\";\n};\n\nexport type FeedbackRewardsResponse = {\n data: FeedbackRewards;\n success: true;\n};\n\nexport type FeedbackStopRequest = {\n /** Exact decimal policy version from the latest experiment; stale updates return 409. */\n expected_version: string;\n};\n\nexport type FeedbackVariant = {\n content_sha256?: string | null;\n creative_id: string;\n model_version?: string | null;\n variant_id: string;\n};\n\n/**\n * A fleet-command job: one batch that fanned a device command across a screen group's\n * members. Poll via GET /fleet-commands/{id}.\n *\n */\nexport type FleetCommand = {\n /** The device command that was fanned out — one of `reload`, `update_settings`, `restart`, `screenshot`, `clear_cache`, `update_content`, `refresh_ads`, `update_config`, `get_state`, `show_ad`, `play_ad`. Validated in the application against `ALLOWED_COMMANDS`, with no database constraint, so the allow-list can grow without a schema migration. */\n command_type?: string;\n /** Set on BOTH terminal states — `completed` and `failed` — so it is not a success marker. `null` only while the job has not reached a terminal state. */\n completed_at?: string | null;\n /** When the job row was inserted, i.e. when the batch was accepted. Because execution is synchronous, `created_at` and `completed_at` bracket the entire fan-out. */\n created_at?: string;\n /** The raw thrown message, set only when the JOB itself failed; `null` otherwise. Per-screen dispatch failures do NOT populate it — those live in `results[].error`, and a job whose targets all failed still reports `status: \"completed\"` with `failed` > 0. */\n error?: string | null;\n /** Screens that could not be enqueued */\n failed?: number;\n /** The `sg_<uuid>` screen group this batch fanned out to. It is a plain TEXT reference, not a foreign key: groups are only ever soft-deleted, so the job outlives its group and keeps the id as an audit record. */\n group_id?: string | null;\n /** Job id: the literal prefix `fc_` followed by a v4 UUID (`pgRepo/fleetCommands.js` mints `fc_${crypto.randomUUID()}`). Opaque — do not parse the UUID half. */\n id?: string;\n /** Constant string `fleet_command` — the Stripe-style type discriminator, present both here and inside the `data` of a `fleet_command.*` webhook. */\n object?: string;\n /** Screens the command was enqueued to */\n queued?: number;\n /** One entry per screen the dispatch touched, persisted only when the fan-out actually produced rows. `results[].status` is `queued` or `failed` — the per-screen dispatch outcome, a different vocabulary from the job's own `status` — and `device_command_id` is the queued device-command's Mongo ObjectId, or `null` when nothing was queued (e.g. `error: \"No device linked\"`). On a `fleet_command.failed` webhook this is always `[]`, because the dispatch never completed. */\n results?: Array<{\n device_command_id?: string | null;\n error?: string | null;\n screen_id?: string;\n status?: \"queued\" | \"failed\";\n }>;\n /** Execution is synchronous inside the create request — the job walks `queued` to `running` to `completed` (or `failed`) before the POST returns — so a poll will in practice only ever observe a terminal value. `queued` and `running` exist for a future async worker that claims `queued` rows. */\n status?: \"queued\" | \"running\" | \"completed\" | \"failed\";\n /** Member screens resolved for this command */\n targeted?: number;\n /** Bumped on each job state transition (`queued` to `running` to terminal), so on a completed job it is effectively the moment the fan-out finished. */\n updated_at?: string;\n};\n\n/**\n * Webhook body for a group fleet-command terminal state (`fleet_command.completed` |\n * `fleet_command.failed`). Standard `{ id, event, timestamp, data }` envelope; `data` is the\n * `fleet_command` job object (status, targeted/queued/failed counts, per-screen results; on\n * failure, an `error` message). The event type is also in the `X-Trillboards-Event` header —\n * verify the compound `X-Trillboards-Signature` over the raw body first.\n *\n */\nexport type FleetCommandEvent = {\n /** The fleet_command job object. */\n data?: Record<string, unknown>;\n /** The terminal state that fired, also sent in the `X-Trillboards-Event` header and mirrored to the SSE stream. Note `fleet_command.completed` fires even when individual screens failed — check `data.failed` and `data.results[]`. Delivery is gated by the `SCREEN_GROUP_EVENTS_ENABLED` runtime flag, off by default. */\n event?: \"fleet_command.completed\" | \"fleet_command.failed\";\n /** `evt_<base36 epoch ms>_<12 hex>`, minted once per occurrence and reused across retries of that delivery — so it is the idempotency key. The same id also goes to every endpoint subscribed to the event, so dedupe per (endpoint, id). It is not the delivery audit id and is not queryable. */\n id?: string;\n /** Stamped when the server enqueued the delivery, not when it was sent; retries keep the original value. It is the emit time of the terminal-state event, which is not the same as the job's `completed_at`. */\n timestamp?: string;\n};\n\n/**\n * Programmatic header-bidding configuration and the resolved VAST waterfall for the screen. The `vast_waterfall.sources[]` array is the programmatic demand path — fetch each source's `vast_url` in priority order and hand the returned VAST to your IMA SDK. Source names are opaque public aliases. Source-health request-pressure governance does not remove SQL/Admin-enabled sources from this catalog.\n *\n */\nexport type HeaderBiddingSettings = {\n /** `null` unless the screen falls in the ad-buffering rollout bucket. `min_buffer`, `max_buffer` and `warmup_count` are COUNTS OF PREFETCHED ADS (defaults 3 / 5 / 5), not seconds or bytes; `max_cache_age_seconds` (default 300) is how long a prefetched VAST may be held before it must be discarded. */\n ad_buffering?: {\n enabled?: boolean;\n max_buffer?: number;\n max_cache_age_seconds?: number;\n min_buffer?: number;\n warmup_count?: number;\n } | null;\n /** `null` unless the screen falls in the ad-pod experiment bucket. When present it carries `enabled`, `pod_size` (how many ads to request in a single pod) and `variant_name` (the pod-size arm this screen hashed into) — the arm is assigned server-side, not chosen by the client. */\n ad_pod?: Record<string, unknown> | null;\n /** Share of ad slots the client should give to programmatic, as a decimal FRACTION 0-1 (`0.20` = 20%), defaulting to `1.0`. Same underlying value the CMS exposes as `programmatic_allocation_percent` (0-100, i.e. this times 100). Only honoured when `request_mode` is `allocation`; under `always` it is ignored. */\n allocation?: number;\n /** One id per waterfall BUILD (one `/ads` response), shared by every entry in `vast_waterfall.sources[]`; the narrower per-source key is `request_id`. This is the value that lands in `vast_requests.auction_request_id`, so it is what groups all the source attempts belonging to a single ad opportunity. Echo it, never mint your own. */\n auction_request_id?: string;\n /** Constant `true` whenever this object is present — the whole `header_bidding_settings` object is `null` for a screen that is not programmatically eligible. This is header-bidding *configuration*, not the serving master switch: that is `programmatic_enabled` (surfaced as `ProgrammaticSettingsResponse.enabled`) plus any active ads-disabled reason, and the legacy `enabled` shadow was deliberately removed from the serving decision. */\n enabled?: boolean;\n /** `+`-joined tags for the yield experiments this screen is bucketed into (e.g. `yield_buffer_treatment+ad_pod_pod_3`), covering ad buffering, fast loop and ad pods; `null` when none is active. Assignment is a consistent hash of the screen id, so it is stable for a screen across requests. Report it back for analytics — on its own it changes no client behaviour. */\n experiment_variant?: string | null;\n /** `null` unless the screen falls in the fast-loop experiment bucket. When present it carries `enabled` and `ad_to_ad_gap_ms` — the gap to leave between consecutive ads, in MILLISECONDS (default 5000), which is the experiment's shortened alternative to the normal ~30s cadence. */\n fast_loop?: Record<string, unknown> | null;\n /** No live producer — `null` on every response. The only assignment is guarded on a partner-level `fetchMode` setting, and no demand partner in the registry defines one, so this key is never populated at either the top level or per source. Do not branch on it. */\n fetch_mode?: string | null;\n /** Legacy predecessor of `request_mode`, retained only as the fallback that selects `always` (true) versus `allocation` (false) when no explicit `request_mode` is stored. An explicit `request_mode` always wins — do not read the two independently or you will disagree with the server on screens that set both. */\n force_request?: boolean;\n /** `null` except on screens in a revenue-recovery experiment arm, where it carries `name` and `reason` plus the IMA overrides to apply for that arm — some subset of `num_redirects`, `vast_load_timeout_ms`, `load_video_timeout_ms`, `source_timeout_ms`, `mime_types`, `enable_preloading`, `sdk_version_override` and `client_hints_override`. The key set is deliberately open: apply the keys you recognise and ignore the rest. */\n ima_profile?: Record<string, unknown> | null;\n /** Minimum gap the client should leave between VAST requests, in SECONDS (not milliseconds), default `30`, accepted range 0-300. Advisory: no server-side check enforces it — the `/ads` rate limiter is a separate mechanism that does not read this value. */\n min_interval_seconds?: number;\n /** Always `null`. Every stored value for this key is JSON `null`, it has no typed home on the screen record (dropped from the typed read path), and the builder emits `hbs.prebid || null` — there is no live producer. Prebid Server demand, when it participates, is spliced into `vast_waterfall.sources[]`, not surfaced here. */\n prebid?: Record<string, unknown> | null;\n /** Opaque public alias of the first waterfall source — the same string as `variant_name` and `source_name`, not the `floodgates`/`prebid`/`other` bidding-provider value the CMS accepts on `ProgrammaticSettingsInput.provider`. The key is absent entirely when the waterfall resolved no sources. */\n provider?: string;\n /** Player tuning selected purely from the screen's `country`; today only `IN` differs from the default — `timeout_ms` 25000 vs 12000, `fallback_timeout_ms` 35000 vs 18000, `vquality` 1 vs 2, `enable_network_adaptation` true vs false, and `ad_buffer_percentage` as a 0-1 fraction of network capacity (0.25 vs 1.0). Note the wire shapes: `preferred_resolution` is an object `{width, height}` and `vquality` an integer, not the strings declared here. */\n regional_config?: {\n ad_buffer_percentage?: number;\n enable_network_adaptation?: boolean;\n fallback_timeout_ms?: number;\n preferred_resolution?: string;\n timeout_ms?: number;\n vquality?: string;\n } | null;\n /** Per-request waterfall identifier; echo it back on programmatic-event reports. */\n request_id?: string;\n /** `allocation` = request only on the `allocation` share of slots; `always` = request on every slot and ignore `allocation`. These are the only two accepted values. Defaults to `always` on the static-tag path; on the waterfall path it defaults from the legacy `force_request` boolean (`always` when true, `allocation` when false). */\n request_mode?: string;\n /** Opaque alias of the source these top-level fields describe — the first waterfall source, identical to `provider`/`variant_name`. Absent when the waterfall resolved no sources. */\n source_name?: string | null;\n /** The first source's `priority` sort key, copied up to the top level. Same semantics as `VastWaterfallSource.priority`: an opaque ascending key that may start at 0 and may be non-contiguous — it is not an index, a count, or a position you can do arithmetic on. */\n source_priority?: number | null;\n /** Opaque public alias of the first waterfall source, byte-identical to `provider` and `source_name` on the same response. Format is `src_<6 hex>` (SHA-256 of the partner key) or `var_<6 hex>` (of the variant name), plus the fixed `src_test` for the sandbox test source and `inbound_dsp` for a collapsed DSP-seat source. Never a real partner name, and it may be rotated per tenant — echo it back on `programmatic-event` reports rather than persisting it as an identity. */\n variant_name?: string;\n /** Client-side VAST/creative caching directive, `null` unless the fleet-level VAST-cache switch is on (shipped default: off). `ttl_seconds` defaults to 21600 (6 hours). `priority` is a fixed rank of `1.5` — between paid ads (1) and self-promo (2) — and is emitted as a NUMBER despite the string type declared here. */\n vast_cache?: {\n enabled?: boolean;\n priority?: string;\n ttl_seconds?: number;\n } | null;\n /** Convenience copy of `vast_waterfall.sources[0].vast_url` — the server overwrites it with the first source on every build and drops the key entirely when the waterfall resolved no sources. It is not an extra tag, so fetching only this URL is exactly the `sources[0]`-and-stop defect the `VastWaterfall` contract warns about. */\n vast_tag_url?: string;\n vast_waterfall?: VastWaterfall;\n} | null;\n\nexport type HttpProbeResult = {\n /** IP address that was probed. The target list is PARASITIC on mDNS — the hosts discovered in the same cycle plus the Wi-Fi gateway, de-duplicated and capped at 10. There is no subnet scan, so a device that advertises nothing on mDNS is never probed. */\n host: string;\n /** Always `80` — the probe only ever tries plain HTTP on port 80, and the value is a literal at the call site rather than something the scanner chose. Printer ports 515 and 9100-9102 are refused before any socket opens, because an HTTP request to a JetDirect/LPD port makes the printer physically spool the bytes. */\n port?: number | null;\n /** The HTTP `Server:` response header from a `HEAD /` request (HTTP/1.0, 1.5 s timeout). Never null in an emitted row — a probe that gets no usable header produces no row, and the value is rejected outright if it is over 256 characters or contains an `@`, angle brackets, control characters, or the words `password`/`passwd`/`secret`/`phpinfo`. Failures are counted separately in `skipReasonCounts`, so absence from this array is not evidence the host is down. */\n server?: string | null;\n [key: string]: unknown;\n};\n\n/**\n * Transitional shape: the legacy `{ success, message }` wrapper with the canonical error OBJECT under `error`. Emitted by partner routes that adopted the canonical `code`/`type` vocabulary without moving to the `TrillboardsError` envelope.\n */\nexport type HybridError = {\n error?: {\n /** Stable machine-readable error code. Switch on this, never on `message`. The 143 CATALOGUED codes are published as the `ErrorCode` schema and served live by `GET /v1/errors`, which is the authority. This field is NOT limited to them: services emit further codes directly, and `ApiError.notFound(resource)` mints `<resource>_not_found` from an arbitrary resource name. Always handle an unrecognised code by falling back to `type`. */\n code?: string;\n param?: string | null;\n type?: ErrorType;\n };\n message?: string;\n success?: boolean;\n};\n\n/**\n * A recorded ad impression / play for a partner screen\n * (`@trillboards/api-client` `Impression`).\n *\n */\nexport type Impression = {\n /** Kind of ad served (e.g. direct, programmatic, self_promo). */\n ad_type?: string;\n advertiser_name?: string;\n /** Effective CPM for the impression, in the partner's currency. */\n cpm?: number;\n /** Sent on the wire as `did`. The server accepts the device's Mongo id, its fingerprint, or the `partner_external_id` you registered for it. An unrecognised value is logged and the call still returns 200 — only a SIGNED impression 404s on an unknown device. */\n device_id: string;\n /** Play duration in seconds. */\n duration_seconds?: number;\n /** Earner revenue accrued for the impression. */\n earnings?: number;\n /** The per-serve id minted by the server on `data.ads[].impression_id` (`imp_<epoch-ms>_<8 hex>`) and echoed back as the `impid` parameter on the impression call — it is the dedup key, so never generate your own. Note that no endpoint currently returns this object: it is the `@trillboards/api-client` type describing what you SEND. */\n impression_id: string;\n /** The batch endpoint reads `timestamp`, NOT `played_at` — a `played_at` you send is ignored and the impression is stamped with server receive time instead. That matters for offline sync: send `timestamp` if the real play time has to survive the upload delay. */\n played_at: string;\n /** Your earner-screen id (24-character hex Mongo ObjectId), sent on the wire as `sid`. It is only a FALLBACK for resolving the screen — the server resolves from the device (`did`) first and reads `sid` solely when no device matched. */\n screen_id: string;\n};\n\n/**\n * Legacy error shape carrying a SCREAMING_SNAKE `code` alongside the message — emitted by the auth middlewares (`middleware/auth.js`, `middleware/partnerUserAuth.js`). Its `code` is NOT drawn from `ErrorCode`; it is a separate, older vocabulary.\n */\nexport type LegacyCodedError = {\n /** Legacy SCREAMING_SNAKE code, e.g. NO_TOKEN, TOKEN_EXPIRED, NO_API_KEY. */\n code?: string;\n message: string;\n success?: boolean;\n};\n\n/**\n * Legacy error shape returned by hand-authored routes that predate the canonical envelope (they call `res.status(n).json(...)` directly rather than routing through `globalErrorHandler`). Documented because it is what these endpoints really return; new endpoints must use `TrillboardsError`.\n */\nexport type LegacyError = {\n error?: string;\n message?: string;\n success?: boolean;\n};\n\nexport type Mcp_create_media_buy_Args = {\n brand?: {\n domain?: string;\n name?: string;\n [key: string]: unknown;\n };\n buy_spec?: {\n audience_profile?: Record<string, unknown>;\n budget?: {\n bid_cpm?: number;\n daily_usd?: number;\n total_usd?: number;\n };\n geo?: {\n city?: string;\n country?: string;\n lat?: number;\n lng?: number;\n radius_km?: number;\n state?: string;\n };\n schedule?: {\n days?: Array<number>;\n end_date?: string;\n hours?: Array<number>;\n start_date?: string;\n };\n screen_ids?: Array<string>;\n venue_types?: Array<string>;\n };\n buyer_ref?: string;\n context?: Record<string, unknown>;\n creative?: {\n duration_seconds?: number;\n type?: \"video\" | \"image\" | \"vast\";\n url?: string;\n };\n end_time?: string;\n idempotency_key?: string;\n name?: string;\n packages?: Array<{\n bid_price?: number;\n budget?: number | {\n amount?: number;\n currency?: string;\n [key: string]: unknown;\n };\n impressions?: number;\n pricing_option_id?: string;\n product_id?: string;\n targeting_overlay?: Record<string, unknown>;\n [key: string]: unknown;\n }>;\n start_time?: string;\n total_budget?: {\n amount?: number;\n currency?: string;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n};\n\nexport type Mcp_discover_inventory_Args = {\n audience_profile?: {\n income?: \"high\" | \"upper_mid\" | \"mid\" | \"lower_mid\" | \"low\";\n lifestyle?: string;\n min_attention?: number;\n };\n limit?: number;\n location?: {\n city?: string;\n lat?: number;\n lng?: number;\n radius_km?: number;\n state?: string;\n };\n venue_types?: Array<string>;\n};\n\nexport type Mcp_get_media_buy_delivery_Args = {\n breakdown_by?: Array<\"screen\" | \"venue\" | \"hour\" | \"audience_segment\">;\n dimensions?: Array<\"screen\" | \"venue\" | \"hour\" | \"audience_segment\" | \"creative\" | \"day\">;\n media_buy_id: string;\n [key: string]: unknown;\n};\n\nexport type Mcp_get_media_buys_Args = {\n context?: Record<string, unknown>;\n include_snapshot?: boolean;\n media_buy_ids?: Array<string>;\n pagination?: {\n cursor?: string;\n max_results?: number;\n };\n status_filter?: \"pending_creatives\" | \"pending_start\" | \"active\" | \"paused\" | \"completed\" | \"rejected\" | \"canceled\" | Array<\"pending_creatives\" | \"pending_start\" | \"active\" | \"paused\" | \"completed\" | \"rejected\" | \"canceled\">;\n [key: string]: unknown;\n};\n\nexport type Mcp_get_products_Args = {\n audience_profile?: {\n income?: string;\n lifestyle?: string;\n min_attention?: number;\n };\n brief?: string;\n buying_mode?: \"brief\" | \"wholesale\" | \"refine\";\n filters?: {\n budget_range?: {\n currency?: string;\n max?: number;\n min?: number;\n };\n channels?: Array<string>;\n countries?: Array<string>;\n delivery_type?: string;\n format_types?: Array<string>;\n venue_types?: Array<string>;\n [key: string]: unknown;\n };\n market?: string;\n pagination?: {\n cursor?: string;\n max_results?: number;\n };\n venue_types?: Array<string>;\n [key: string]: unknown;\n};\n\nexport type Mcp_list_accounts_Args = {\n account?: {\n account_id?: string;\n brand?: {\n brand_id?: string;\n domain: string;\n };\n operator?: string;\n sandbox?: boolean;\n };\n context?: Record<string, unknown>;\n pagination?: {\n cursor?: string;\n max_results?: number;\n };\n sandbox?: boolean;\n status?: \"active\" | \"pending_approval\" | \"rejected\" | \"payment_required\" | \"suspended\" | \"closed\";\n [key: string]: unknown;\n};\n\nexport type Mcp_sync_accounts_Args = {\n accounts: Array<{\n account?: {\n account_id?: string;\n brand?: {\n brand_id?: string;\n domain: string;\n };\n operator?: string;\n sandbox?: boolean;\n };\n billing?: \"operator\" | \"agent\" | \"advertiser\";\n billing_entity?: Record<string, unknown>;\n brand?: {\n brand_id?: string;\n domain: string;\n };\n notification_configs?: Array<Record<string, unknown>>;\n operator?: string;\n payment_terms?: \"net_15\" | \"net_30\" | \"net_45\" | \"net_60\" | \"net_90\" | \"prepay\";\n preferred_reporting_protocol?: string;\n sandbox?: boolean;\n }>;\n context?: Record<string, unknown>;\n delete_missing?: boolean;\n dry_run?: boolean;\n idempotency_key: string;\n push_notification_config?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\nexport type Mcp_sync_creatives_Args = {\n account?: {\n account_id?: string;\n brand?: {\n brand_id?: string;\n domain: string;\n };\n operator?: string;\n sandbox?: boolean;\n };\n assignments?: Array<{\n creative_id: string;\n media_buy_id?: string;\n package_id?: string;\n [key: string]: unknown;\n }>;\n context?: Record<string, unknown>;\n creative_ids?: Array<string>;\n creatives: Array<{\n assets?: Record<string, unknown>;\n creative_id?: string;\n duration_seconds?: number;\n file_size_mb?: number;\n format_id?: {\n agent_url: string;\n duration_ms?: number;\n height?: number;\n id: string;\n width?: number;\n } | string;\n format_kind?: string;\n height?: number;\n name?: string;\n type?: \"video\" | \"image\" | \"vast\";\n url?: string;\n width?: number;\n [key: string]: unknown;\n }>;\n delete_missing?: boolean;\n dry_run?: boolean;\n ext?: Record<string, unknown>;\n idempotency_key?: string;\n media_buy_id?: string;\n push_notification_config?: Record<string, unknown>;\n validation_mode?: string;\n [key: string]: unknown;\n};\n\nexport type Mcp_update_media_buy_Args = {\n account?: Record<string, unknown>;\n canceled?: boolean;\n cancellation_reason?: string;\n context?: Record<string, unknown>;\n end_time?: string;\n ext?: Record<string, unknown>;\n idempotency_key?: string;\n invoice_recipient?: Record<string, unknown>;\n media_buy_id: string;\n new_packages?: Array<Record<string, unknown>>;\n packages?: Array<{\n creative_assignments?: Array<{\n creative_id: string;\n [key: string]: unknown;\n }>;\n package_id: string;\n targeting_overlay?: Record<string, unknown> | null;\n [key: string]: unknown;\n }>;\n paused?: boolean;\n push_notification_config?: Record<string, unknown>;\n reporting_webhook?: Record<string, unknown>;\n revision?: number;\n start_time?: string | string;\n updates?: {\n budget?: {\n bid_cpm?: number;\n daily_usd?: number;\n total_usd?: number;\n };\n end_date?: string;\n name?: string;\n screen_ids?: Array<string>;\n start_date?: string;\n status?: \"active\" | \"paused\" | \"cancelled\";\n targeting?: Record<string, unknown>;\n };\n [key: string]: unknown;\n};\n\nexport type MdnsNetworkDevice = {\n /** The responder's IP address as a string — the first IPv4 address if it has one, otherwise any address. A record that resolves to no address is dropped rather than emitted with a null host, so in practice this is always populated. It is also what seeds the HTTP-probe target list for the same discovery cycle. */\n host?: string | null;\n /** The service instance name verbatim from the resolve event. A PII filter runs first and DROPS THE WHOLE ROW when the name looks like a personal name (an apostrophe-s possessive, an email, a MAC), so devices named after people disappear from the list entirely rather than appearing anonymised. */\n instanceName: string;\n /** Model string lifted from the TXT record using a per-service-type key table — `md` for Chromecast and HAP, `model`/`am` for AirPlay, `ty`/`product` for IPP, `dt` for Matter (a device-TYPE code used as a model surrogate, not a model name), with a broader default list for other types. `null` when no key in that list is present or all their values are blank. */\n mdnsModel?: string | null;\n /** Version string from the TXT record. Frequently this is `txtvers` — the TXT RECORD FORMAT version, not the device firmware version — because that key is in the fallback list for most service types. `null` when no candidate key is present. */\n mdnsSoftwareVersion?: string | null;\n /** Vendor string from the TXT record, by the same per-type key table. Two traps: Chromecast has an EMPTY vendor key list, so it is always `null` there; and for HAP this carries the HomeKit `ci` CATEGORY id (a numeric device class) while for Matter it carries the raw `VP` `vendorid+productid` pair — neither is a vendor name. */\n mdnsVendor?: string | null;\n /** TCP/UDP port advertised in the SRV record. A port of 0 is coerced to `null` rather than forwarded, so `null` means \"not advertised\", never \"port zero\". */\n port?: number | null;\n /** The mDNS service type the record resolved under, e.g. `_googlecast._tcp.`. Discovery is not open-ended — the scanner browses a FIXED list of 19 types (Roku, AirPlay, RAOP, Chromecast, Amazon WPlay, Android TV Remote, Spotify Connect, device-info, companion-link, IPP/IPPS/printer/PDL, HAP, Matter, Alexa, HTTP and the Trillboards CSI type), so a device advertising anything else is invisible here. */\n serviceType: string;\n [key: string]: unknown;\n};\n\n/**\n * Versioned public disclosure of how audience measurement is produced.\n * Describes WHAT is measured and HOW IT IS QUALIFIED — deliberately never\n * names infrastructure or source locations.\n *\n */\nexport type MeasurementMethodology = {\n /** Prose description of the happy-path pipeline: edge capture, buffered ingest, `signal_observations`, a 30-minute device-resolution pass into `resolved_device_observations`, then a 30-minute transition pass. Names the stores only — never infrastructure or source locations. */\n canonical_path?: string;\n /** What each screen observes and sends. */\n capture_stack?: Record<string, unknown>;\n /** Cross-day identity, including measured coverage (1.6% of identifiable BLE rows). An earlier revision said ~31%; that graded the presence of stable_payload_hash and ignored the sanitizer the identity expression applies, and it is withdrawn. */\n cross_day_matching?: Record<string, unknown>;\n /** The in-range contour: BLE RSSI at or above -75 dBm, empirically about 5 m line-of-sight for Class-2 radios at typical mounting. Calibration is static per screen-class — there is no per-screen ranging today — and devices inside the contour classified as stationary are excluded from the audience funnel by default. */\n display_exposure_zone?: Record<string, unknown>;\n /** What promotes a raw in-range observation to a qualifying event, one entry per qualification (`ble_engagement`, `face_attention`, `returning_visitor`), each carrying its definition, its rationale, and the ClickHouse field a reviewer re-derives it from. */\n dwell_qualification_thresholds?: Record<string, unknown>;\n /** Static disclosure of how observation confidence decays: `algorithm` (Bayesian posterior with temporal decay), `decay_window_days` (90) and `max_confidence` (0.95). These are published constants, not per-request measurements. */\n edge_confidence_model?: Record<string, unknown>;\n /** Evidence grades, which are decisionable, and person-grain resolution. */\n evidence_grade_ladder: Record<string, unknown>;\n /** Prose description of degraded ingest: observations buffer on a durable queue and replay on recovery, and after repeated failures land in a dead-letter store rather than being dropped. */\n fallback_path?: string;\n /** How identifiers are keyed, and what erasure means. */\n hashing?: Record<string, unknown>;\n /** The counting UNIT. Reach and unique-device counts are counts of distinct canonical BLE keys (identifiers); one device contributes several identifiers per window and the number grows with the window. Carries the measured identifiers-per-device per fleet type and the controlled comparison it was derived from. */\n identifier_inflation?: Record<string, unknown>;\n /** Alignment against MRC OOH Phase 2. `mrc_status_accredited` is the machine-readable answer and is **false until an MRC grant reference exists** — read that boolean rather than parsing the English `status` line. Also carries the monotonic qualification chain (`in_range` through `cross_screen`) and the interval method (Wilson score for proportions, 200-sample bootstrap for medians). */\n mrc_ooh_phase2_alignment: Record<string, unknown>;\n /** Version markers for the edge parsers that produced the disclosed fields — `ble_advertisement_parser`, `mdns_txt_extractor`, and the integer `heartbeat_payload_schema`. Hand-maintained constants, bumped when a parser changes. */\n parser_versions?: Record<string, unknown>;\n /** Per-store retention in days in active systems; `basis` states how each is counted and what the encrypted archive keeps. */\n retention: Record<string, unknown>;\n /** Tables a reviewer re-derives every published count from. */\n source_tables: Array<string>;\n /** Bumped on any disclosure change; drives the ETag. */\n version: string;\n};\n\n/**\n * Revenue per screen per hour. Aggregates gross revenue from completed impressions. Refreshed from CH via clickhouseRollupSource. PG only.\n */\nexport type OpenrtbRevenueScreenHourly = {\n actual_gross_revenue: number;\n estimated_gross_revenue: number;\n estimated_impressions: number;\n gross_revenue: number;\n hour_bucket: string;\n impressions: number;\n priced_impressions: number;\n screen_mongo_id: string;\n updated_at: string;\n};\n\nexport type Pagination = {\n /** Whether more items exist */\n has_more?: boolean;\n /** Items per page */\n limit?: number;\n /** Current offset */\n offset?: number;\n /** Total number of items */\n total?: number;\n};\n\nexport type Partner = {\n /** Defaults to the literal `lite_sdk` when unset, so it is never null, and is `lite_sdk` for effectively the whole fleet. It is load-bearing at serve time: `lite_sdk`/`full_sdk` mark the partner a video-only SDK consumer, so an image creative is swapped for the ad's partner video URL unless `sdk_config.allowed_ad_types` includes `image`. */\n integration_tier?: string;\n /** Registered company name. The same string is the buyer-facing media-owner label for this partner's inventory, so it is what advertisers see in the media-owner picker. */\n name?: string;\n /** The 24-character hex Mongo-era identifier (`partners.mongo_partner_id`), not the table's integer `partner_id`. This is the value the API key's owner id and every `partner_mongo_id` foreign key carry. */\n partner_id?: string;\n /** Legacy classification retained for existing integrations. New integrations should not branch on this field. */\n partner_type?: string;\n /** The partner's share as a percentage 0-100 (the stored rate is 0-1 and is multiplied by 100 here). Resolved from the active, currently-effective `publisher_payout_rates` rule; with no such rule it falls through to the system default and reports `60` — which is also why the `POST /register` response echoes `60` even when the caller asked for 70, since the partner row is read back before the rate row is written. */\n revenue_share_percent?: number;\n sdk_config?: SDKConfig;\n /** Lower-cased at registration and unique across all partners (a collision is rejected `409`). It appears in device embed URLs and portal links, so it is effectively permanent once devices are provisioned. */\n slug?: string;\n stats?: PartnerStats;\n /** In practice **always `active` here**: the API-key resolver refuses any partner whose status is not `active`, so a `pending`/`suspended`/`terminated` partner gets `401 invalid_api_key` and never reaches this response. Registration also always creates `active`. */\n status?: \"pending\" | \"active\" | \"suspended\" | \"terminated\";\n /** Defaults to the literal `pending` when unset. `POST /earnings/payout` refuses any value other than `active`. */\n stripe_account_status?: string;\n};\n\nexport type PartnerAdDeliveryProfile = {\n /** Android SDK API level as reported by the device; `null` when not reported, and then no Android rule fires at all. Below 23 forces `mode: vast_fallback` and turns `auto_recovery_enabled` off. 23-25 with desugaring reported off only appends a warning to `notes` and leaves the mode alone. */\n android_api_level?: number | null;\n /** Whether the SERVER may auto-enqueue recovery device commands for this device — the `refresh_ads` and `restart` it issues when a screen heartbeats online without recent VAST requests, or with stalled playback. It is not a client-side setting, and it goes false alongside every `vast_fallback` verdict so we do not restart a device whose player we already know is the wrong one. Even when true, a recovery command is queued only for a device that acts on commands, meaning one that acknowledged a command (`POST /device/{deviceId}/command/{commandId}/ack`) in the last 7 days, or that was handed none in that time. A device that let a delivered command expire unacknowledged, and acknowledged none, is sent no recovery command until that command is 7 days old. */\n auto_recovery_enabled?: boolean;\n /** Echoed back from the device's own heartbeat telemetry, or `unknown` when the device never reported one. It selects which compatibility rules run: `android_native` enables the Android API-level gate, `html5_webview` the Chromium-version checks; `disabled` and `unknown` skip both, so no mode change can result from those. */\n ima_integration?: \"android_native\" | \"html5_webview\" | \"disabled\" | \"unknown\";\n /** The server's per-heartbeat verdict on how this device should play ads: `ima_sdk` is the normal IMA path, `vast_fallback` means bypass IMA entirely and play the VAST in your own fallback player. It is recomputed from the telemetry on every heartbeat, so treat it as a live instruction rather than one-time provisioning. */\n mode?: \"ima_sdk\" | \"vast_fallback\";\n /** Human-readable diagnostics accompanying the decision above; an empty array on a healthy device. They are free prose for logs and support tickets, not a stable enum — nothing in the contract depends on matching their text, and their presence alone does not imply degraded delivery. */\n notes?: Array<string>;\n /** Machine token for why `mode` was chosen: `default` (nothing disqualified the device), `sdk_reported_ima_unsupported` (your own telemetry reported `imaSupported: false`), or `android_api_below_23_for_current_ima_android_sdk`. `default` is always what accompanies `ima_sdk`. */\n reason?: string;\n /** Major Chromium version of the device's WebView, `null` when not reported. Advisory only — it never changes `mode`: below 87 appends a content-restoration note and below 80 an additional autoplay/media note. */\n webview_chromium_major?: number | null;\n};\n\n/**\n * Non-fatal advisories about the creative that was just accepted. The ad IS created and will serve — these describe problems only the uploader can fix. Empty when there is nothing to report. The most important one is an over-length video: a spot longer than the 30 second ad slot is cut off when the next ad starts, so it never reaches its completion event and its final seconds are never seen.\n */\nexport type PartnerCreativeWarnings = Array<string>;\n\nexport type PartnerDefaultStream = {\n /** The stream's 24-hex id, minted server-side with `newObjectId()` at create and stored as `default_streams.mongo_default_stream_id`. It is the value every other default-stream route takes as `{streamId}`; the shape is Mongo's but PostgreSQL is the store of record. */\n _id?: string;\n /** Accepted with no required-field check, same as `google_slides_url`. The derived `canva_design_id` is returned alongside it although this schema does not declare it. */\n canva_url?: string | null;\n /** Mapped from `default_streams.created_at`. It is the list endpoint's secondary sort key, so two streams sharing a `playback_order` play oldest-first. */\n createdAt?: string;\n /** Falls back to `name`, then to `Background content`; the upload and image routes use `Uploaded video content` / `Image carousel content` instead. */\n description?: string;\n /** Seconds one pass of this stream occupies. It is computed as `images.length x image_duration` when you send images without it; for every other type it is only ever what you sent — nothing probes a YouTube or uploaded video to fill it in on this path. */\n duration_seconds?: number | null;\n /** The URL the player actually loads into the iframe. It defaults to `generic_iframe_url` when you do not send it, so on an iframe stream the two are usually equal. */\n embed_url?: string | null;\n /** The iframe source for the `generic_iframe` type. It also seeds `embed_url` when that is omitted, so sending only this one populates both fields. */\n generic_iframe_url?: string | null;\n /** Unlike `youtube`/`twitch`/`uploaded`/`images`, the `google_slides` type has NO required-field check — a create naming that type with no URL is accepted, stores null, and the screen then renders an empty frame. */\n google_slides_url?: string | null;\n /** Only five keys survive: `auto_advance`, `slide_delay_ms`, `refresh_interval_seconds`, `sandbox_flags`, `allow_flags` — anything else is silently discarded and a non-object is discarded whole. On update it is MERGED over the stored config rather than replacing it, so a key cannot be cleared by omitting it. */\n iframe_config?: Record<string, unknown> | null;\n /** Seconds each image is held before advancing. The multipart image route defaults it to 10 when omitted and uses it to compute `duration_seconds`; the JSON create leaves it unset unless you send it. */\n image_duration?: number | null;\n /** Ordered image URLs, each held for `image_duration` seconds. Required and non-empty when `stream_type` is `images` (400 otherwise), null for every other type; the create accepts a real array, a JSON-encoded array string, or a comma-separated string. */\n images?: Array<string>;\n /** Soft-deactivation flag. The list filters `is_active = true`, so it is always `true` on anything this API returns; the partner DELETE removes the row outright rather than clearing this flag, which is instead used by the screen-wide deactivate paths. */\n is_active?: boolean;\n /** At most one stream per screen carries this — promoting or reordering clears it on every sibling first. A create sets it on the new stream, `reorder` gives it to `order[0]`, and deleting the current stream hands it to whatever the ordering resolves next. */\n is_currently_playing?: boolean;\n /** Falls back to `description`, then to the literal `Default Stream`, so it is never empty. The multipart upload route instead falls back to the uploaded file's original name, and the multi-image route to `Image Carousel (N images)`. */\n name?: string;\n /** Present ONLY on the 201 from the three create routes, which echo the in-memory document back. `default_streams` has no partner column, so list, update and read responses omit this key entirely — tenancy is enforced at the screen, not on the stream row. */\n partner_id?: string;\n /** 1-based position in the screen's rotation; the list sorts on it and breaks ties with `createdAt`. A newly created stream is promoted to 1 and every sibling shifts down, and `POST .../reorder` rewrites it to each id's index in the `order` array you send. */\n playback_order?: number;\n /** The owning screen, mapped from `default_streams.screen_mongo_id` — the same 24-hex id you passed as `{screenId}` in the path. Note the camelCase spelling inside an otherwise snake_case object. */\n screenId?: string;\n /** Accepted with no required-field check. The derived `social_wall_provider` and `social_wall_widget_id` are returned alongside it although this schema does not declare them. */\n social_wall_url?: string;\n /** Which renderer the screen uses. When omitted at create it is DERIVED from whichever content field you sent, in this fixed order: `images`, `youtube_url`, `twitch_url`, `google_slides_url`, `canva_url`, `social_wall_url`, `generic_iframe_url`/`embed_url`, `video_url` (`uploaded`), `local_file_path` (`local`) — and the create is rejected 400 if none resolves. Only `youtube`, `uploaded`, `images` and `twitch` are ever content-analyzed; the four embed types deliberately keep `gemini_analyzed_at` NULL. */\n stream_type?: string;\n /** Required when `stream_type` is `twitch` (400 otherwise). The response also carries the undeclared `twitch_embed_url`, `twitch_content_type` and `twitch_id` companions that the PG reshape returns. */\n twitch_url?: string | null;\n /** Mapped from `default_streams.updated_at`. Playback bookkeeping writes it — marking a stream current, clearing a stale current flag, or stamping `last_played_at` — so it moves without anyone editing the stream content. */\n updatedAt?: string;\n /** Required when `stream_type` is `uploaded`. The `/default-streams/upload` route sets it for you to the stored object URL of the file you posted (`req.file.location`), so you never send it on that path. */\n video_url?: string | null;\n /** Required when `stream_type` is `youtube` — the create is rejected 400 (`youtube_url is required for youtube streams`) without it. Null on every other stream type. */\n youtube_url?: string | null;\n};\n\nexport type PartnerDefaultStreamCreate = {\n /** Accepted with no required-field check, same as `google_slides_url` — a `canva` create with this omitted succeeds and renders nothing. */\n canva_url?: string;\n /** Optional. Falls back to `name` and then to `Background content`. */\n description?: string;\n /** Optional explicit duration for iframe-based streams */\n duration_seconds?: number;\n /** The URL actually loaded into the iframe. When omitted it is filled from `generic_iframe_url`, so send it only when the embeddable URL differs from the one you want recorded. */\n embed_url?: string;\n /** The iframe source. Sending it also derives `stream_type: generic_iframe` and seeds `embed_url`, so this one field is enough to create an iframe stream. */\n generic_iframe_url?: string;\n /** The `google_slides` type has NO required-field check, so a create naming that type without this URL is accepted and stores null. Send a published-to-web slides URL. */\n google_slides_url?: string;\n /** Only these five keys are kept — every other key you send is silently dropped, a non-object is dropped whole, and `slide_delay_ms`/`refresh_interval_seconds` must be finite numbers or they are dropped individually. On a later update this object is merged over the stored one rather than replacing it. */\n iframe_config?: {\n allow_flags?: string;\n auto_advance?: boolean;\n refresh_interval_seconds?: number;\n sandbox_flags?: string;\n slide_delay_ms?: number;\n };\n /** Seconds per image */\n image_duration?: number;\n /** Required and non-empty for an `images` stream. Accepted as a real array, a JSON-encoded array string, or a comma-separated string; combined with `image_duration` it also derives `duration_seconds` when you do not send one. */\n images?: Array<string>;\n /** Optional. Falls back to `description` and then to the literal `Default Stream`, so the created stream always has one. */\n name?: string;\n /** Accepted with no required-field check — a `social_wall` create with this omitted succeeds and renders nothing. */\n social_wall_url?: string;\n /** Optional — omit it and the server DERIVES it from whichever content field you sent, checking in this order: `images`, `youtube_url`, `twitch_url`, `google_slides_url`, `canva_url`, `social_wall_url`, `generic_iframe_url`/`embed_url`, `video_url` (`uploaded`), `local_file_path` (`local`). If nothing resolves the request is rejected 400 `No supported stream content provided`. */\n stream_type?: \"local\" | \"youtube\" | \"uploaded\" | \"images\" | \"twitch\" | \"google_slides\" | \"canva\" | \"social_wall\" | \"generic_iframe\";\n /** Required when the resolved `stream_type` is `twitch`; the create is rejected 400 without it. */\n twitch_url?: string;\n /** Required when the resolved `stream_type` is `uploaded`. Use `POST .../default-streams/upload` with a multipart file instead if you want us to host the video — that route fills this in for you. */\n video_url?: string;\n /** Required when the resolved `stream_type` is `youtube`; the create is rejected 400 without it. Sending it alone is enough — the type is derived. */\n youtube_url?: string;\n};\n\nexport type PartnerDefaultStreamUpdate = PartnerDefaultStreamCreate;\n\n/**\n * Batch device-registration body (`@trillboards/api-client`\n * `PartnerDeviceBatchRegistrationInput`) — up to 100 devices with optional\n * fleet-wide `defaults`. `defaults` applies to every device in the batch\n * except the per-device identifier fields (`device_id` / `device_name`).\n *\n */\nexport type PartnerDeviceBatchRegistrationInput = {\n /** Fleet-wide defaults applied to each device except its identifier fields. */\n defaults?: PartnerDeviceRegistrationInput;\n /** 1 to 100 entries; more than 100 rejects the whole request with `Maximum 100 devices per batch request`. The batch is NOT transactional — entries are registered sequentially and independently, so the call can return 2xx with some entries reported under `errors[]` while the rest committed. An entry whose `device_id` already exists is an update, counted under `summary.updated` rather than `summary.created`. */\n devices: Array<PartnerDeviceRegistrationInput>;\n};\n\n/**\n * Heartbeat payload from agent-core / agent-core-lite Android partner\n * SDKs. Only includes fields verified populated in production\n * `signal_observations` (last 30 days). Stub fields wired in Kotlin but\n * never observed in CH are intentionally excluded; re-add only after\n * telemetry confirms >1% population over 7 days.\n *\n */\nexport type PartnerDeviceHeartbeatBody = {\n /** Legacy flat advertising identifier, superseded by the `advertising_identity` envelope. It is only honoured when no canonical envelope is present AND it passes a plausibility check; a value that fails the check is dropped without an error, and it never reaches the device record through the generic device snapshot — identity has one dedicated persistence rail. */\n advertisingId?: string | null;\n /** Type of the legacy flat `advertisingId`. It is retained only when `advertisingId` itself survives the plausibility check — otherwise it is nulled with it — and the canonical `advertising_identity.id_type` enum is wider (`gaid`, `aaid`, `afai`, `rida`, `tifa`, `idfa`) than the two values allowed here. */\n advertisingIdType?: \"gaid\" | \"idfa\" | null | null;\n advertising_identity?: AdvertisingIdentity;\n /** Number of distinct BLE devices seen in the scan window, BEFORE the 50-entry cap on `nearbyBleDevices` — so it can legitimately exceed the array length (the on-device scanner tracks up to 100 groups). Omit it and the server substitutes the array length. Both this and the snake_case `ble_device_count` are accepted. */\n bleDeviceCount?: number;\n capabilities?: DeviceCapabilityPayload;\n /** mDNS/Bonjour service instances found on the LAN, capped at 50. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`discovered_network_devices`). */\n discoveredNetworkDevices?: Array<MdnsNetworkDevice> | null;\n /** Up to 16 audience identities (hashed email / UID2) keyed by `(id_type, source)`. Rejection is all-or-nothing — one bad element or a 17th entry drops the whole array — and the row-level upsert additionally refuses any envelope whose `observed_at` is older than the stored one, so a replayed batch is a silent no-op. */\n extended_identities?: Array<ExtendedIdentity>;\n /** The agent's stable device fingerprint. On this endpoint it is inert: the path parameter `deviceId` is the sole binding boundary, and the partner heartbeat controller never reads `fingerprint` from the body — it uses the fingerprint of the device the path already resolved. */\n fingerprint?: string;\n /** Hash of the LAN default-gateway IP — the venue-network join key, and the seed the HTTP probe adds to its mDNS-derived target list. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`gateway_ip_hash`). */\n gatewayIpHash?: string | null;\n /** Results of HTTP HEAD probes against at most 10 hosts — the mDNS hosts found in the same cycle plus the Wi-Fi gateway, port 80 only. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`http_probes`). */\n httpProbes?: Array<HttpProbeResult> | null;\n /** Legacy flat LAT flag. `true` alone is enough to clear a previously stored identifier — the server synthesises a `limited` identity envelope from it even with no `advertisingId` present — so do not send `true` speculatively. */\n limitAdTracking?: boolean | null;\n /** Alternate namespace for the same telemetry keys as `telemetry`, merged UNDER it (`{...metadata, ...telemetry}`), so `telemetry` wins on a collision. This is where the Android agents actually put their telemetry — `uptimeSeconds`, `agentType`, `agentVersion`, screen geometry, `sensingMode` — and where the per-heartbeat CPU/memory/thermal gauges are merged in. */\n metadata?: Record<string, unknown>;\n /** Capped at 50 devices (RSSI-sorted, strongest first). */\n nearbyBleDevices?: Array<BleScanResultData> | null;\n /** Wi-Fi scan results, capped at 50 with the joined AP pinned first. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`nearby_wifi_networks`, whose inner keys are `raw_bssid`/`signal_dbm`/`frequency_mhz` — note `signal_dbm`, not a snake_case form of `signalStrengthDbm`). */\n nearbyWifiNetworks?: Array<WifiScanResult> | null;\n /** Count of mDNS instances discovered, before the 50-entry cap (the Rust scanner itself keeps at most 30). Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`network_device_count`). */\n networkDeviceCount?: number;\n /** Heartbeat schema version (1=baseline, 2=nativeSensorSnapshot) */\n schemaVersion?: number;\n /** The screen the agent believes it is bound to. Also inert on this endpoint — the server resolves the screen from the device record, never from this value; correct a wrong binding through registration or the socket heartbeat, which returns an authoritative screen id. */\n screenId?: string | null;\n /** Per-source skip-reason counter map. Shape `{source: {reason: count}}`. */\n skipReasonCounts?: Record<string, Record<string, number>>;\n /** UPnP/SSDP responders found by one `M-SEARCH` with `ST: ssdp:all`, deduped by LOCATION and capped at 50. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`ssdp_devices`). */\n ssdpDevices?: Array<SsdpDeviceInfo> | null;\n /** Agent lifecycle word — the Android agents hardcode `\"active\"` on every heartbeat, so it carries no information. The partner heartbeat controller does not read it; online/standby state is derived from `telemetry.powerState`, not from this field. */\n status?: string;\n /** Hash of the joined access point's BSSID. This field is accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`wifi_bssid_hash`). */\n wifiBssidHash?: string | null;\n wifiEnvironment?: WifiEnvironmentSnapshot;\n /** Count of distinct access points seen, before the 50-entry cap on `nearbyWifiNetworks`. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`wifi_network_count`). */\n wifiNetworkCount?: number;\n /** Hash of the joined network's SSID. Same as `wifiBssidHash`: accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`wifi_ssid_hash`). */\n wifiSsidHash?: string | null;\n [key: string]: unknown;\n};\n\n/**\n * A play reported by the partner SDK's reportPlay. The PartnerPlay body without screen_id (the screen is the device's binding), plus the Trillboards impression id.\n */\nexport type PartnerDevicePlay = {\n /** Optional ad/line identifier; defaults to the sha-keyed creative. */\n ad_id?: string;\n /** When the play completed (device clock), within the last 7 days and at most 5 minutes ahead of the server clock. */\n completed_at: string;\n /** The classification reference from POST /creatives — `sha256:<hex>` or the bare 64-char content hash. */\n creative_ref: string;\n /** Play duration in seconds (default 30). */\n duration_seconds?: number;\n /** The Trillboards impression id, when the host played an ad Trillboards served. The Trillboards record of that play is then kept and this report is not counted twice. */\n impression_id?: string;\n /** Your unique id for this play (idempotency key, shared with POST /v1/partner/plays). */\n play_id: string;\n /** Optional request identifier. */\n request_id?: string;\n};\n\n/**\n * Request body accepted by the single-device registration endpoints\n * (`@trillboards/api-client` `PartnerDeviceRegistrationInput`). Additional\n * vendor-specific fields are accepted and forwarded to enrichment.\n *\n */\nexport type PartnerDeviceRegistrationInput = {\n /** Stable partner-owned device identifier. */\n device_id?: string;\n /** Legacy SDK alias for the device identifier, retained for backwards compatibility. */\n device_name?: string;\n /** Mutable partner category stored independently of advertising classification. Defaults to other on a new device; omission on an existing device preserves its value. Unsupported categories return HTTP 400. */\n device_type?: \"tv\" | \"tablet\" | \"display\" | \"kiosk\" | \"other\" | \"mobile\" | \"connected_tv\" | \"digital_signage\" | \"vending_machine\";\n /** Display specification (pixel resolution and/or physical size). */\n display?: Record<string, unknown>;\n display_settings?: PartnerRegistrationDisplaySettings;\n /** Location hints (lat/lng, address, venue type). */\n location?: Record<string, unknown>;\n /** Friendly name for the device. */\n name?: string;\n /** Placement hints for the screen within the venue. */\n placement?: Record<string, unknown>;\n /** Venue taxonomy hints. */\n venue?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\nexport type PartnerHeartbeatCommand = {\n /** Command normalized for partner SDK compatibility. */\n command?: string;\n /** Opaque command identifier — echo it verbatim to `POST /device/{deviceId}/command/{commandId}/ack`. The ack is ownership-checked against the device that received it, so acknowledging another device's command is refused with 403. */\n id?: string;\n /** Command arguments, shape determined by `command` (for example a brightness level or a target URL). Always an object — commands that take no arguments carry `{}`, never null. */\n payload?: Record<string, unknown>;\n /** When the command was enqueued, not when it was dispatched to you — the gap is up to one heartbeat interval, and a large gap means the device was silent, not that the server was slow. */\n queued_at?: string;\n};\n\nexport type PartnerHeartbeatData = {\n ad_delivery_profile?: PartnerAdDeliveryProfile;\n /** Pending remote commands, at most 5 per heartbeat, drained from the PG `device_commands` store. Each is marked `sent` as it is handed to you, so it appears in exactly ONE heartbeat response and is not re-delivered — acknowledge every entry via the ack endpoint, and dedupe on `id` because delivery is at-most-once, not exactly-once. */\n commands?: Array<PartnerHeartbeatCommand>;\n /** Server-clock timestamp stamped when this heartbeat was processed — the same instant used for the liveness write, not a device-supplied time and not the time of a previous heartbeat. */\n last_seen_at?: string;\n /** Liveness verdict for the screen. In practice it is `online`, or `standby` when the heartbeat reported `power_state` of `off`/`standby` — `standby` is NOT in the declared enum, and `offline` can never be returned here because receiving this response is itself proof the device reached us. It falls back to `online` when the device is not yet linked to a screen. */\n status?: \"online\" | \"offline\";\n};\n\nexport type PartnerHeartbeatMeasurementMetadata = {\n /** Whole seconds since `receivedAt`, computed when the response was built and floored at 0 — the same number as the sibling `stalenessSeconds`. It measures the age of the HEARTBEAT, not of the cache entry; use `cachedAt` for cache age. */\n ageSeconds?: number | null;\n /** When the Redis device twin that served this response was written. It is non-null ONLY when `source` is `redis_twin`, and even then only if the twin carried a `cachedAt`/`observedAt` stamp; the `pg_fallback` and `unknown` paths always report `null`. */\n cachedAt?: string | null;\n /** ISO-8601 server-clock time the underlying heartbeat was received — byte-for-byte the same value as the sibling `lastHeartbeatAt`, not an independent measurement. `null` when no heartbeat has ever been recorded. */\n receivedAt?: string | null;\n /** Which store answered: `redis_twin` (the hot pre-computed device twin — the normal path), `pg_fallback` (the twin was cold or missing, so the persisted last-heartbeat record was used), or `unknown` (no heartbeat has ever been recorded). This is the field that determines which keys `telemetry` can contain, and it is the only signal that the twin was a miss. */\n source?: \"redis_twin\" | \"pg_fallback\" | \"unknown\";\n [key: string]: unknown;\n};\n\nexport type PartnerHeartbeatQueryData = {\n adDeliveryProfile?: PartnerAdDeliveryProfile;\n /** The device's canonical identifier, which need NOT equal the value you put in the path: the server echoes the device fingerprint, falling back to your registered `external_id` and then to the internal id. Match responses on your own request, not on this field. */\n deviceId: string;\n /** Server-clock time the most recent heartbeat was RECEIVED — never a device-supplied timestamp. `null` means this device has never sent a heartbeat that the server retained; note the value can come from the Redis twin, which expires, so a cold cache falls back to the persisted record rather than reporting null. */\n lastHeartbeatAt?: string | null;\n measurementMetadata?: PartnerHeartbeatMeasurementMetadata;\n /** Whole seconds between `lastHeartbeatAt` and the moment this response was built, floored at 0 and rounded — it is computed at read time, so two calls a minute apart return different values for the same heartbeat. `null` when `lastHeartbeatAt` is null. It duplicates `measurementMetadata.ageSeconds` exactly. */\n stalenessSeconds?: number | null;\n telemetry?: PartnerSafeHeartbeatTelemetry;\n [key: string]: unknown;\n};\n\nexport type PartnerHeartbeatQueryResponse = {\n data?: PartnerHeartbeatQueryData;\n /** Always `true` on this 200 response. The 400 (missing `deviceId`), 404 (unknown device), 403 (device belongs to another partner) and 500 paths return `success: false` with a `message` and no `data`. */\n success?: boolean;\n};\n\n/**\n * Optional heartbeat payload. Nested telemetry objects are preferred; legacy flat keys\n * are still accepted for backward compatibility. The registered\n * `deviceId` is the binding boundary for both liveness and identity; no\n * separate partner API key is required.\n *\n */\nexport type PartnerHeartbeatRequest = {\n /** Highest-precedence source for `last_vast_request_at`, `last_vast_response_at` and `proof_of_play_enabled`. All three are dead on both sides — no agent or SDK in the repo emits this block, and nothing reads the values once stored. */\n ad?: {\n last_vast_request_at?: string;\n last_vast_response_at?: string;\n proof_of_play_enabled?: boolean;\n };\n advertising_identity?: AdvertisingIdentity;\n /** Flat alias of `telemetry.agentStatus`. No producer uses this spelling; live values arrive camelCase and come from two disjoint vocabularies — `sensing`/`camera_failed`/`starting`/`idle` from the Android sensing SDK, `foreground`/`background` from the browser SDK. */\n agent_status?: string;\n /** Second-precedence source (after `telemetry.agentVersion`) for the Trillboards agent/SDK version — this is the key the Android sensing SDK actually sends on the partner heartbeat. It is kept ONLY if the heartbeat self-identifies as a Trillboards SDK; otherwise the server force-nulls it so a BYO player's app version is never recorded as ours. */\n agent_version?: string;\n /** Android SDK/API level; below 23 forces `vast_fallback` for a native-Android IMA host. The shipped browser SDK derives it from the user-agent token `Android <n>`, which is the platform RELEASE number (Android 9 gives 9), not the API level (28) — so a value under ~23 is more likely a release number than a genuinely old device. */\n android_api_level?: number;\n /** Flat alias of `telemetry.brightness`, on the platform's own 0-100 scale. Only the Tizen and webOS agents emit a brightness at all, and they put it in `metadata`, not here. */\n brightness?: number;\n /** Java 8+ library desugaring in a native-Android IMA host. Nothing in the repo emits it in any casing, so it is absent fleet-wide; it is read only when `ima_integration` is `android_native` and `android_api_level` is 25 or below, where `false` adds a crash-risk note to `ad_delivery_profile.notes`. */\n desugaring_enabled?: boolean;\n /** Hardware self-description. `os`/`osv`/`make`/`model` here rank below `telemetry`/`metadata` and above the flat body keys, while `android_api_level` and `webview_chromium_major` are read from here in snake_case. It also carries the undocumented `native_bridge` object — the Android WebView bridge's ground-truth make/model/OS/advertising identity — and the DOOH taxonomy keys `dth`/`msid`/`venuetype`/`rdid`/`idtype`/`is_lat`. */\n device?: {\n android_api_level?: number;\n make?: string;\n model?: string;\n os?: string;\n osv?: string;\n webview_chromium_major?: number;\n };\n /** Up to 16 DSP-matchable audience identities (hashed email / UID2). Normalisation is ALL-OR-NOTHING across the array: one malformed element, a duplicate `(id_type, source)` pair beyond the first, or more than 16 entries causes the ENTIRE array to be dropped and the heartbeat still answers 200. Send `[]`-worth of care — a partially valid batch stores nothing. */\n extended_identities?: Array<ExtendedIdentity>;\n /** Flat alias, outranked by `ima.integration`. `html5_webview` marks the heartbeat as coming from a Trillboards SDK (which is what permits `agent_version` to be stored); `android_native` with an API level below 23 forces `vast_fallback`. Unrecognised or absent values become `unknown`, never null. */\n ima_integration?: \"android_native\" | \"html5_webview\" | \"disabled\" | \"unknown\";\n /** Flat alias with no producer — the browser SDK sends `ima.sdk_version` (from `window.google.ima.VERSION`), which outranks this key and is `null` when IMA failed to load. */\n ima_sdk_version?: string;\n /** Flat alias with no producer — the browser SDK sends `ima.support_reason`, the literal `ok` on success or its IMA load-failure reason. Stored only; the ad-delivery decision never reads it. */\n ima_support_reason?: string;\n /** Flat alias, outranked by `ima.supported` and by `telemetry`/`metadata`. An explicit `false` pins `ad_delivery_profile.mode` to `vast_fallback` and turns `auto_recovery_enabled` off, so the server stops attempting to move the device back onto IMA. */\n ima_supported?: boolean;\n /** Flat alias of `telemetry.inputSource`. Dead on both sides — no producer emits either casing, and the column has no ClickHouse mirror. */\n input_source?: string;\n /** Numeric IMA `AdError` code from the last IMA failure. The browser SDK sends it inside `ima`, which outranks this flat key; it is persisted on the device record and read by nothing. */\n last_ima_error_code?: number;\n /** Numeric VAST error code from the last tag failure (e.g. 303 for an empty wrapper chain), sent inside `ima` by the browser SDK. Persisted only, with no server-side consumer. */\n last_vast_error_code?: number;\n /** Timestamp of the last outbound VAST request. No producer exists anywhere in the repo and nothing reads the stored value, so this field is dead on both ends. */\n last_vast_request_at?: string;\n /** Timestamp of the last VAST response received. Like `last_vast_request_at`, no agent or SDK emits it and nothing consumes it. */\n last_vast_response_at?: string;\n /** Current GPS fix for a moving screen. Processed only for screens whose `mobility_type` is `mobile` or `semi_mobile` (declare it at registration or via PATCH /device/{deviceId}), OR for any screen still missing coordinates (a one-time self-heal that also kicks off venue classification). Static screens with valid coordinates ignore it. Updates live geo-targeting, geofencing and analytics. Fully optional — omit it on stationary screens. */\n location?: {\n /** Horizontal accuracy radius in meters, stored as the device reports it. */\n accuracy_m?: number;\n /** Up to 200 fixes recorded while the device was offline, replayed as a historical track. Timestamps MUST be in ascending order — a non-chronological batch is rejected with HTTP 400. Only the top-level `location` updates the live serving position. */\n buffered_locations?: Array<{\n accuracy_m?: number;\n heading?: number;\n lat: number;\n lng: number;\n speed_mps?: number;\n timestamp?: number;\n }>;\n /** Bearing in degrees (0–360, 0 = true north). Omit if unavailable. */\n heading?: number;\n /** Latitude in decimal degrees (WGS 84). */\n lat?: number;\n /** Longitude in decimal degrees (WGS 84). */\n lng?: number;\n /** `gps` — a live on-device fix. `gps_buffered` — a fix recorded while offline and replayed on reconnect. */\n source?: \"gps\" | \"gps_buffered\";\n /** Ground speed in meters per second. Omit if unavailable. */\n speed_mps?: number;\n /** Unix epoch milliseconds of the on-device fix. Send the device clock value. A fix whose timestamp is more than 60 s ahead of the server's clock, or more than 6 h behind it, is kept without a fix time. A position jump implying > 320 km/h between consecutive fixes is rejected. */\n timestamp?: number;\n };\n /** Alternate namespace for telemetry keys (legacy support). */\n metadata?: Record<string, unknown>;\n /** Flat alias of `telemetry.muted`. The TV agents fall back to `false` here when power state is unreadable while falling back to `null` for `brightness`/`volume`, so a `false` cannot be read as evidence the read succeeded. */\n muted?: boolean;\n /** Connection metrics. The server reads ONLY the camelCase keys shown here; the shipped `trillboards-lite` browser SDK sends this block snake_case (`connection_type`, `effective_type`, `bandwidth_mbps`, `latency_rtt`), so not one key matches and its entire network block is silently discarded on every heartbeat. `bandwidthMbps`/`downlinkMbps` share one slot and `latencyRtt`/`rtt` share another. */\n network?: {\n bandwidthMbps?: number;\n connectionType?: string;\n downlinkMbps?: number;\n effectiveType?: string;\n latencyRtt?: number;\n rtt?: number;\n saveData?: boolean;\n };\n /** Flat alias of `telemetry.networkStrengthDbm`, in dBm (negative). No agent or SDK populates either spelling, so it is absent fleet-wide. */\n network_strength_dbm?: number;\n /** Flat alias of `telemetry.powerState`, used only when the nested value is absent. It coerces to `unknown` rather than failing, and `off`/`standby` downgrade the screen to `standby` so it stops receiving ads. No shipped producer uses this snake_case spelling — every emitter sends the camelCase form. */\n power_state?: \"on\" | \"standby\" | \"off\" | \"unknown\";\n /** Declares that the player emits proof-of-play. No producer sends it and no server logic reads it — proof-of-play eligibility is decided server-side, not from this flag. */\n proof_of_play_enabled?: boolean;\n /** Lowest-precedence source for the IMA/build fields: `ima_supported`, `ima_integration`, `ima_sdk_version`, `android_api_level` and `webview_chromium_major` are read from here only after `ima.*`, `telemetry`/`metadata` and the flat body keys have all missed. Its `version` sub-key is NOT read at all — the SDK version the server stores comes from body `agent_version`/`sdk_version`. */\n sdk?: {\n android_api_level?: number;\n desugaring_enabled?: boolean;\n ima_integration?: \"android_native\" | \"html5_webview\" | \"disabled\" | \"unknown\";\n ima_sdk_version?: string;\n ima_supported?: boolean;\n version?: string;\n webview_chromium_major?: number;\n };\n /** Third-precedence alias of `agent_version` — same destination and the same BYO-nulling rule. This is what the shipped `trillboards-lite` browser SDK sends, alongside `sdk_type: \"lite\"`, which is itself one of the signals that classify the heartbeat as ours. */\n sdk_version?: string;\n telemetry?: PartnerHeartbeatTelemetry;\n /** Flat alias of `telemetry.temperatureC`, in degrees Celsius from the Android battery thermal sensor. No producer uses this snake_case spelling, and battery-less CTV hardware omits the reading altogether rather than sending 0. */\n temperature_c?: number;\n /** Flat alias of `telemetry.uptimeSeconds`. It measures seconds since DEVICE boot (`SystemClock.elapsedRealtime()/1000` on Android — it survives an agent restart and counts through deep sleep), not agent process uptime. Producers send the camelCase form inside `telemetry`/`metadata`. */\n uptime_seconds?: number;\n /** Flat alias of `telemetry.volume`, on the platform's own scale (0-100 on Tizen/webOS), not a percentage of any documented maximum. Emitted by the Tizen and webOS agents through `metadata` only. */\n volume?: number;\n /** Chromium major version behind the WebView, parsed from the `Chrome/<n>` user-agent token. Below 87 and below 80 each append an advisory note to `ad_delivery_profile.notes`; neither changes the delivery mode. */\n webview_chromium_major?: number;\n};\n\nexport type PartnerHeartbeatResponse = {\n data?: PartnerHeartbeatData;\n /** Always `true` on this 200 response. The failure paths — 404 for an unresolvable `deviceId`, 400 for an invalid BLE payload, 500 on an internal error — return `success: false` with a `message` and no `data` key at all. */\n success?: boolean;\n};\n\nexport type PartnerHeartbeatTelemetry = {\n /** A status word whose vocabulary depends on which SDK reports it: the Android sensing SDK derives it from camera health and sends exactly `sensing` (camera open), `camera_failed`, `starting` or `idle`, while the browser SDK sends `foreground`/`background` from `document.hidden`. Those four Android values are also an in-band signal that the heartbeat came from a Trillboards SDK, which is what allows `agentVersion` to be stored. */\n agentStatus?: string;\n /** Version of the TRILLBOARDS agent/SDK only. If the heartbeat does not self-identify as one of ours (`ima.integration=html5_webview`, `sdk_type=lite`, a `capabilities.audienceSensing.cameraHealth` block, or one of the four sensing `agentStatus` values), the server discards what you send and force-nulls both casings, so a BYO player's own app version is never stamped as ours. Resolution order is `telemetry.agentVersion`, then body `agent_version`, then body `sdk_version`. */\n agentVersion?: string;\n /** Android SDK/API level (23 = Marshmallow, 28 = Pie); below 23 it forces `vast_fallback` when `ima_integration` is `android_native`. Beware the unit — the shipped browser SDK derives it from the user-agent token `Android <n>`, which is the platform RELEASE number (Android 9 gives 9), not the API level (28). */\n android_api_level?: number;\n /** Downlink bandwidth in MEGABITS PER SECOND from `navigator.connection.downlink`. It shares one destination with `downlinkMbps` (`bandwidthMbps` wins), then the same two keys inside `network`, so only one value ever survives. */\n bandwidthMbps?: number;\n /** Panel backlight level on the platform's own scale — on Samsung Tizen and LG webOS the 0-100 picture-brightness value, `null` when that picture-control service is unavailable (VIDAA hardcodes `null`, the API does not exist there). Neither partner SDK sends it; the `brightness` seen in the Android agents is a command-ACK detail, not heartbeat telemetry. */\n brightness?: number;\n /** Free-text connection label (`wifi`, `ethernet`, `cellular`…) that becomes `networkMetrics.connectionType`; `networkConnectionType` outranks it and `network.connectionType` is the fallback. It does NOT set the OpenRTB `device.connectiontype` sent in bid requests — that is the separate all-lowercase `connectiontype` key, which is an INTEGER enum (0 unknown, 1 ethernet, 2 wifi, 3 cellular, 4 2G, 5 3G, 6 4G, 7 5G), is not declared in this schema, and only passes because the object allows additional properties. */\n connectionType?: string;\n /** Whether Java 8+ library desugaring is enabled in a native-Android IMA host. No agent or SDK anywhere in the repo emits it, so it is absent fleet-wide; it is consulted only when `ima_integration` is `android_native` and `android_api_level` is 25 or below, where `false` adds a known-crash-risk note to `ad_delivery_profile.notes`. */\n desugaring_enabled?: boolean;\n /** Second-precedence spelling of `bandwidthMbps` (Mbps) filling the same `networkMetrics.bandwidthMbps` slot; ignored whenever `bandwidthMbps` is also present. The npm SDK sends both from the same `connection.downlink` reading. */\n downlinkMbps?: number;\n /** How the player reaches Google IMA. `html5_webview` is hardcoded by both Trillboards browser SDKs and is itself the signal marking the heartbeat as ours (which is what allows `agentVersion` to be stored); `android_native` with `android_api_level` below 23 forces `ad_delivery_profile.mode` to `vast_fallback`. Absent or unrecognised values normalise to `unknown`, never null, and the undocumented nested `ima.integration` key — what the shipped browser SDK actually sends — outranks this flat one. */\n ima_integration?: \"android_native\" | \"html5_webview\" | \"disabled\" | \"unknown\";\n /** The IMA SDK's own version string. Nothing produces this flat spelling — the shipped browser SDK sends it as `ima.sdk_version`, read from `window.google.ima.VERSION`, which takes precedence — and it is `null` whenever IMA failed to load, so absence usually pairs with `ima_supported: false`. */\n ima_sdk_version?: string;\n /** Free-text explanation for the `ima_supported` verdict — the browser SDK sends the literal `ok` when IMA loaded and its load-failure reason otherwise, under `ima.support_reason`, which outranks this flat spelling (nothing produces the flat one). Stored only: the ad-delivery decision never reads it. */\n ima_support_reason?: string;\n /** Whether the IMA SDK is usable on this player. An explicit `false` is a hard switch, not a hint: it pins `ad_delivery_profile.mode` to `vast_fallback` AND sets `auto_recovery_enabled: false`, so the server stops trying to move the device back onto IMA. The browser SDKs compute it as `Boolean(window.google.ima)` / an IMA load-failure flag. */\n ima_supported?: boolean;\n /** Free-form active-input label (e.g. an HDMI port name), stored verbatim on the device record. It has NO producer: no agent or SDK in the repo emits either casing, the only other references are Flutter UI localisation strings, and the schema sidecar marks the column PG-only with no ClickHouse mirror. Treat it as permanently absent. */\n inputSource?: string;\n /** Numeric IMA `AdError` code from the most recent IMA failure, cached client-side between heartbeats. The shipped browser SDK sends it inside `ima`, which outranks this flat key. Persisted on the device record only — no server logic reads it. */\n last_ima_error_code?: number;\n /** Numeric VAST error code from the most recent tag failure (e.g. 303 for a wrapper chain that resolved to nothing), cached client-side and sent inside `ima` by the browser SDK. Persisted only, with no server-side consumer. */\n last_vast_error_code?: number;\n /** Timestamp of the player's last outbound VAST request. It has NO producer: a repo-wide grep finds only the server-side reader and the generated API client, so it is absent on every live device, and nothing downstream consumes the stored value. */\n last_vast_request_at?: string;\n /** Timestamp of the last VAST response the player received. Like `last_vast_request_at` it has no producer in any agent or SDK, and the persisted value has no consumer. */\n last_vast_response_at?: string;\n /** Round-trip time in MILLISECONDS from `navigator.connection.rtt`. Shares one destination with `rtt` (`latencyRtt` wins); the read-side schema bounds the stored value to 0-60000. */\n latencyRtt?: number;\n /** Device manufacturer. Written to the device column but not authoritative — every heartbeat runs a device-identity fusion pass that can replace `make`/`model`/`os` from the user agent and 51Degrees specs, and the corrected value is what is persisted and read back. */\n make?: string;\n /** Device model, subject to the same identity-fusion overwrite as `make`. The browser SDK has no real model string and sends the full user agent in this field instead. */\n model?: string;\n /** Audio mute state. Note the asymmetry with `brightness`/`volume`: when the Tizen/webOS agent cannot read power state it still sends `false` here while sending `null` for the other two, so `false` does not distinguish \"not muted\" from \"could not be read\". The `muted` the browser SDK sends elsewhere belongs to the ad request, not the heartbeat. */\n muted?: boolean;\n /** Highest-precedence spelling of the free-text connection label that becomes `networkMetrics.connectionType`. No agent or SDK emits it, so in practice the value comes from `connectionType` or `network.connectionType`. Sending any of the six network keys makes the server stamp `networkMetrics.measuredAt` and increment `networkMetrics.measurementCount`. */\n networkConnectionType?: string;\n /** Highest-precedence spelling of the Network Information API `effectiveType` bucket (`slow-2g`/`2g`/`3g`/`4g`) — a round-trip-time and downlink classification, not the physical bearer. No producer emits this spelling; live values arrive as `effectiveType` or `network.effectiveType`. */\n networkEffectiveType?: string;\n /** Wi-Fi/cellular RSSI in dBm (negative; the read-side schema bounds it to -120..0). No agent or SDK populates it — the field exists on the Kotlin model and the only value in the tree is a hardcoded test literal — so it is absent fleet-wide. Per-AP signal is instead carried by `nearbyWifiNetworks[].signalStrengthDbm` and `wifiEnvironment.connectedSignalDbm`. */\n networkStrengthDbm?: number;\n /** Operating-system family, written straight to the device column `os` — the Android agents send the full `\"Android <release>\"` string, not a bare family name. It is an input to the identity-fusion pass that runs on every heartbeat, so what the read endpoint later returns may be a corrected value rather than what you sent. */\n os?: string;\n /** Lower-precedence alias of `osv`: both resolve to the same normalized value and the same `osv` device column, with `osv` winning when both are present. */\n osVersion?: string;\n /** OS version string. `osv` and `osVersion` are two spellings of ONE value — the normalizer takes `osv` first and falls back to `osVersion`, and both land in the single device column `osv`, so sending different values in the two keys silently keeps only `osv`. */\n osv?: string;\n /** Display power as the device reports it. Never null and never rejected: any unrecognised or absent value is coerced to `unknown`, and `off`/`standby` make the server record the screen `standby` rather than `online`, which stops ad delivery. Producers use this camelCase spelling only — the browser SDK derives it from `document.hidden` (`standby` when hidden, else `on`), the Fire TV / Android TV agents send their `PowerController` state; the flat `power_state` alias has no producer at all. */\n powerState?: \"on\" | \"standby\" | \"off\" | \"unknown\";\n /** Whether the player is configured to emit proof-of-play. No agent or SDK sends it, so it is absent fleet-wide, and nothing reads the stored value — actual proof-of-play eligibility is decided server-side, never from this flag. */\n proof_of_play_enabled?: boolean;\n /** Second-precedence spelling of `latencyRtt` (milliseconds), ignored when `latencyRtt` is also present. */\n rtt?: number;\n /** The browser's Data Saver preference (`navigator.connection.saveData`). Accepted as a boolean or the strings `\"true\"`/`\"false\"`; anything else becomes `null` rather than `false`. */\n saveData?: boolean;\n /** Degrees Celsius from the BATTERY thermal sensor — the only real °C reading a non-system Android app can obtain. Battery-less CTV hardware (Fire TV sticks, TV boxes) omits the key entirely rather than sending 0, so absence means \"no battery sensor\", not \"cool\"; the derived ClickHouse `cpu_temp_c` was measured above zero on ZERO rows. */\n temperatureC?: number;\n /** Seconds since the DEVICE booted, not since the agent process started: the Android producers send `SystemClock.elapsedRealtime() / 1000`, which keeps counting through deep sleep and resets only on reboot, so an agent crash-loop leaves this climbing. It is also the sole camelCase survivor in the ClickHouse heartbeat blob — every other gauge there is snake_case. */\n uptimeSeconds?: number;\n /** Speaker volume on the platform's own scale — 0-100 on Tizen (`tizen.tvaudiocontrol.getVolume()`) and webOS, `null` when the audio-control service is unavailable or (on VIDAA) does not exist. It is not normalised to a percentage of any maximum, and neither partner SDK sends it. */\n volume?: number;\n /** Major version of the Chromium engine backing the WebView, parsed from the `Chrome/<n>` user-agent token by both browser SDKs. Below 87 adds a content-restoration warning to `ad_delivery_profile.notes` and below 80 an autoplay/media warning; neither changes the delivery mode. */\n webview_chromium_major?: number;\n [key: string]: unknown;\n};\n\n/**\n * What a rider did with the screen, reported by the partner SDK's reportInteraction. (partner, device, interaction_id) is the event's identity.\n */\nexport type PartnerInteraction = {\n /** The creative on screen, as the host names it. */\n creative_id?: string;\n /** The host app's id for this interaction (idempotency key, per device). */\n interaction_id: string;\n /** When it happened (device clock), within the last 7 days and at most 5 minutes ahead of the server clock. */\n occurred_at: string;\n /** Milliseconds into the creative's playback when it happened. */\n t_ms?: number;\n /** What happened. seatbelt_reminder_shown = the host app showed a SeatbeltReminder that agent-core 1.24 delivered to its safety-event listener; send the reminder's eventId as interaction_id. Never a programmatic ad-lifecycle event (those are POST /device/{deviceId}/programmatic-event). */\n type: \"tap\" | \"qr_shown\" | \"qr_scanned\" | \"dwell_prompt\" | \"seatbelt_reminder_shown\";\n};\n\nexport type PartnerLBarContent = {\n /** The venue name rendered in the L-bar; empty string when unset. One of only three scalar keys (`enabled`, `business_name`, `current_promotion`) the PUT copies straight across — every other key here is an object that gets shallow-merged. */\n business_name?: string;\n /** Holds `phone`, `email`, `website`, `address` and `wifi_password`, all free text and all empty-string when unset, shallow-merged on PUT. `wifi_password` is rendered on the screen, so treat it as public. */\n contact_info?: Record<string, unknown>;\n /** The promo line shown in the L-bar. It is also writable through the much lighter `PUT .../l-bar/live-updates` endpoint as its `promotion` field, which is the path to use for frequent changes. */\n current_promotion?: string;\n /** Free-text lines rotated in the L-bar. Unlike every nested object above, this is REPLACED wholesale whenever it is present in the body, so send the complete list each time. */\n custom_messages?: Array<string>;\n /** The L-bar's own cosmetics — NOT the screen display-settings resource. Holds `background_color` (`#000000`), `text_color` (`#FFFFFF`), `accent_color` (`#4A90E2`), the widget toggles `show_weather`/`show_time`/`show_date`/`show_store_hours` (default true) and `show_contact_info`/`show_social_media`/`show_sports_scores`/`show_stock_market` (default false), plus `selected_sports_leagues`. Shallow-merged on PUT. */\n display_settings?: Record<string, unknown>;\n /** Whether the L-bar content region is drawn; defaults `false`. It is COUPLED to `display_preferences.display_mode` on the PUT — sending `display_mode: \"fullscreen\"` forces this to `false`, and `\"l-bar\"` forces it to `true` unless you send an explicit boolean in the same request. */\n enabled?: boolean;\n /** Exactly three sub-objects are merged — `queue_management` (`enabled`, `current_number`, `queue_message`, default `Now Serving`), `wait_time` (`enabled`, `current_wait_minutes`, `wait_message`, default `Current Wait Time`) and `custom_status` (`enabled`, `status_message`); any other key nested here is silently dropped. The live-updates endpoint sets `enabled` for you as a side effect of writing a value, and clears `custom_status.enabled` when you send an empty message. */\n live_updates?: Record<string, unknown>;\n /** Holds `facebook`, `instagram`, `twitter` and `google_reviews`, free text and empty-string when unset, shallow-merged on PUT. */\n social_media?: Record<string, unknown>;\n /** Free-text opening hours: one string per lowercase day name (`monday`..`sunday`) plus a `timezone` defaulting to `America/New_York`. The strings are displayed verbatim — nothing parses them into open/close times — and the PUT shallow-merges them, so you can send a single day. */\n store_hours?: Record<string, unknown>;\n};\n\nexport type PartnerLBarDisplayPreferences = {\n /** Which surfaces ads may occupy; `null` by default, in which case it is derived from `display_mode` + `l_bar_ads_enabled`. Setting `lbar` also flips `display_mode` to `l-bar` when the same request does not set it, and narrows the fullscreen surface to `self_promo` only — paid and programmatic demand stops running full screen. */\n ad_surface_mode?: \"fullscreen\" | \"lbar\" | \"pip\" | \"all\" | null | null;\n /** Default `fullscreen`. Writing it has side effects: `fullscreen` also forces `l_bar_ads_enabled` and `l_bar_content.enabled` to `false`, and `l-bar` forces both to `true` unless the same request sets them explicitly. A change also emits a `refresh` control event so the kiosk reloads and swaps surfaces. */\n display_mode?: \"fullscreen\" | \"l-bar\";\n /** Seconds one L-bar ad is held; default 8, accepted range 3-30. It must be strictly LESS than `l_bar_ad_interval`, and the check falls back to the stored interval when this request does not send one. */\n l_bar_ad_duration?: number;\n /** Seconds between L-bar ad plays; default 25, accepted range 10-120. It must be strictly GREATER than `l_bar_ad_duration`, and the pair is cross-checked against the STORED values, so lowering this alone can fail on a duration set by an earlier request. */\n l_bar_ad_interval?: number;\n /** Whether ads may fill the L-bar slots. When it has never been set it is DERIVED as `display_mode === \"l-bar\"`, so it reads `true` on an L-bar screen and `false` on a fullscreen one. Accepts a boolean or the strings `\"true\"`/`\"false\"`; anything else is 400. */\n l_bar_ads_enabled?: boolean;\n /** Which edge the L-bar occupies; default `bottom`. It decides ad-slot geometry — `bottom` lays slots out horizontally and `right` vertically — and the slot COUNT is derived from it, so switching sides changes how many `lbar_slot_*` surfaces exist. This endpoint does not validate the value against any enum (the display-settings resource's `l_bar.position` does, and accepts `top`/`bottom`/`left`/`right`). */\n l_bar_position?: \"bottom\" | \"right\";\n /** Share of the screen the L-bar occupies, as a PERCENT; default 20. This endpoint accepts it with no range check at all, while the equivalent `l_bar.size_percent` on the display-settings resource clamps to 10-50. It is also not what sizes ad slots — `buildAdSurfaces` uses a fixed 25%. */\n l_bar_size?: number;\n /** Picture-in-picture ad window: `enabled` (default false), `position` (default `bottom-right`), `size_ratio` as a FRACTION of the screen 0.15-0.45 (default 0.3), `aspect_ratio` as width/height 0.5-3 (default 16/9), and `margin` in PIXELS 8-64 (default 24). A PiP surface is only produced when `ad_surface_mode` is `pip` or `all` AND `enabled` is true. */\n pip_settings?: {\n aspect_ratio?: number;\n enabled?: boolean;\n margin?: number;\n position?: \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\n size_ratio?: number;\n };\n};\n\nexport type PartnerLBarSettings = {\n /** The resolved renderable ad surfaces, one entry per surface (`fullscreen`, `lbar_slot_1..N`, `pip`). Present ONLY on the PUT response; the GET does not compute it. */\n ad_surfaces?: Array<{\n allowed_ad_types?: Array<string>;\n dimensions?: {\n height?: number;\n width?: number;\n };\n display_area_ratio?: number;\n display_mode?: string;\n enabled?: boolean;\n id?: string;\n surface_group?: string;\n surface_type?: string;\n }>;\n display_preferences?: PartnerLBarDisplayPreferences;\n l_bar_content?: PartnerLBarContent;\n /** The screen's resolved pacing contract, present only on the PUT response. `policy_version` is `v1` and every interval is in MILLISECONDS (`paid_interval_ms`, `self_promo_interval_ms`, `image_ad_total_ms`, `image_ad_min_ms`); it also carries `max_paid_share` (null unless a loop share cap is enabled), `surface_policy`, `schedule_policy`, `pip_settings` and `lbar_settings`. */\n playback_policy?: Record<string, unknown>;\n};\n\n/**\n * Where the screen's location comes from, and, per rail, whether that rail\n * has the location it needs. Location is a readiness capability, never a\n * registration rule: a device without one is registered, and this says\n * which rails serve it until a position arrives. `monetization_status` is\n * computed over `inventory_feed` and `geo_targeted_demand`.\n *\n */\nexport type PartnerLocationCapability = {\n /** `precise` — the screen or its device has coordinates; `fleet_market` — none of its own, your account's declared fleet market location lists it; `none`. */\n position?: \"precise\" | \"fleet_market\" | \"none\";\n rails?: {\n geo_targeted_demand?: PartnerLocationRail;\n inventory_feed?: PartnerLocationRail;\n visit_attribution?: PartnerLocationRail;\n };\n};\n\nexport type PartnerLocationRail = {\n /** Where a ready rail's location comes from; null when blocked. Only `inventory_feed` reads `fleet_market`. */\n location_source?: \"screen\" | \"device\" | \"fleet_market\" | null | null;\n /** Why a blocked rail has no location; null when ready. See https://api.trillboards.com/docs/monetization-requirements#location-capability. */\n reason?: \"awaiting_device_gps\" | \"no_location_provided\" | \"city_without_state\" | \"geocode_failed\" | \"screen_position_pending\" | \"country_unknown\" | \"country_not_supported\" | null | null;\n status?: \"ready\" | \"blocked\";\n};\n\n/**\n * Stripe-style monetization requirements (account.requirements analogue).\n * Returned on device register / update / get / batch so bulk partners can\n * detect and self-remediate missing data instead of discovering silent\n * gaps later. `currently_due` blocks or limits monetization now;\n * `eventually_due` improves serving but isn't blocking.\n *\n */\nexport type PartnerMonetizationRequirements = {\n /** Requirements blocking or limiting monetization right now. */\n currently_due?: Array<PartnerRequirement>;\n /** Non-blocking improvements (e.g. display dimensions). */\n eventually_due?: Array<PartnerRequirement>;\n};\n\nexport type PartnerNetworkConfig = {\n /** Partner-level ad pacing and identity controls applied to every screen you own; on the `/ads` path they OUTRANK the screen's own `ad_interval_seconds` and the partner's `sdk_config.max_ads_per_hour`. A PATCH merges key-by-key and rejects unknown keys with a 400, and sending `null` for `identity_mode` or `self_promo_priority_mode` DELETES the key rather than storing null, so those two come back absent rather than as `null`. */\n ad_delivery?: {\n /** `/ads` response cache lifetime. Zero requires a fresh response for every request. */\n cache_max_age_seconds?: number;\n /** Delegates resettable advertising identity to the native Google IMA SDK; the API never fabricates one. Set null to restore the platform default identity path. */\n identity_mode?: \"ima_sdk\" | null;\n /** Fleet-wide hourly ad cap returned in `settings.max_ads_per_hour`. */\n max_ads_per_hour?: number;\n /** Requests non-personalized demand when true. Never provide an advertising identifier unless the OS legitimately exposes it. */\n non_personalized_ads?: boolean;\n /** Returned as both `settings.ad_interval` and `header_bidding_settings.min_interval_seconds`; the player should request each monetizable slot at this cadence. */\n request_interval_seconds?: number;\n /** Controls whether a scheduled self-promo/house slot may lead the VAST waterfall. `scheduled_primary` preserves the bounded house cadence; `fallback_only` keeps self-promo behind paid programmatic demand; `disabled` removes the self-promo/house creative from your screens entirely — it is served on neither `ads[]` nor the VAST waterfall, and no new house creative is generated for your screens. Paid-direct guarantees always retain priority and are never affected. Set null to restore `scheduled_primary`. */\n self_promo_priority_mode?: \"scheduled_primary\" | \"fallback_only\" | \"disabled\" | null;\n };\n /** White-label presentation for your buyer-facing advertiser portal. These values are read and written directly against the branding your portal renders — a GET returns what a buyer currently sees, and a write takes effect on their next page load. Omitted entirely if no white-label portal is linked to your partner account; writing branding in that state returns `409` `white_label_distributor_not_linked` rather than accepting values that would render nowhere. Contact Trillboards to have one linked. */\n branding?: {\n /** Shown as the portal name and the browser tab title. */\n display_name?: string;\n /** Absolute HTTPS URL. */\n favicon_url?: string;\n font_body?: string;\n font_heading?: string;\n /** Absolute HTTPS URL. Rendered in the portal header. */\n logo_url?: string;\n /** CSS color. HSL triplets (\"210 90% 40%\") and hex are both accepted. */\n primary_color?: string;\n secondary_color?: string;\n tagline?: string;\n };\n /** Default content preferences inherited by newly provisioned screens. */\n default_content_preferences?: Record<string, unknown>;\n /** Partner-level (fleet-wide) demand-source ordering; there is no per-screen override for these keys. A PATCH merges key-by-key into what is stored, and any key other than `source_order`/`total_timeout_ms` is rejected with a 400 naming it. The GET omits this object entirely when nothing has been stored, so absence means \"never configured\" — it is not a rendering of the effective defaults. */\n default_waterfall?: {\n /** Preferred opaque source aliases from the `/ads` response. Sources not listed here remain enabled and are appended. Internal demand names and `sort_by: cpm` are rejected. */\n source_order?: Array<string>;\n /** Total deadline for one waterfall snapshot, in milliseconds. */\n total_timeout_ms?: number;\n };\n /** Limits your branded buyer portal to YOUR OWN screens. Off by default: a branded portal otherwise sells the whole Trillboards network, which is what most white-label partners want. The screen set is DERIVED from the media-owner identity your screens already carry — both the ones we host and the ones resold through partner exchanges — so it stays correct as you add inventory and there is no list to maintain. It cannot be set to another operator's inventory. */\n storefront?: {\n /** When true, every inventory surface in your buyers' portal — search, map, forecasts, quotes, bookings and proposals — is limited to your own screens. */\n enabled?: boolean;\n };\n};\n\n/**\n * Partner-safe partial update. Internal `partners`, `supply_chain`, seller\n * IDs, direct demand names, and CPM-sort controls are rejected.\n *\n */\nexport type PartnerNetworkConfigUpdate = PartnerNetworkConfig;\n\n/**\n * A log-level play for a partner-served ad.\n */\nexport type PartnerPlay = {\n /** Optional ad/line identifier; defaults to the sha-keyed creative when creative_ref is sent. */\n ad_id?: string;\n /** ISO 8601 timestamp when the play completed. */\n completed_at: string;\n /** Optional. The classification reference from POST /creatives — `sha256:<hex>` or the bare 64-char content hash. Omit it for a play of an ad you hold no content hash for; the play is then recorded with its `ad_id`, or with none. */\n creative_ref?: string;\n /** Play duration in seconds (default 30). */\n duration_seconds?: number;\n /** Your unique id for this play (idempotency key). */\n play_id: string;\n /** Optional partner request identifier. */\n request_id?: string;\n /** One of your registered earner-screen ids. */\n screen_id: string;\n};\n\n/**\n * Partner-reported impression and revenue counts for reconciliation against deduped completed_impressions. Ingested from VidVerto SSP API and Adipolo API / JustBaat CSV reports. PG only.\n */\nexport type PartnerReconciliationReports = {\n adjustment_id?: number | null;\n applied_at?: string | null;\n /** applied_report_hash/applied_at (mig 449) gate the hourly pricing re-apply: syncs skip the CI re-stamp + PED rebuild when the applied tuple is unchanged. Written ONLY by partnerPricingApplyGate.reco... */\n applied_report_hash?: string | null;\n created_at: string;\n /** Wave 2 B added demand_partner_id INT + variant_id INT FKs. Legacy partner_key TEXT kept as wire-format alternate-key. Backfill via follow-up Wave 2 B-backfill PR. */\n demand_partner_id?: number | null;\n /** VidVerto synced daily via vidvertoRevenueSync before programmatic earnings aggregation. */\n id?: number | null;\n imported_by?: string | null;\n /** NUMERIC(8,4). */\n impression_variance?: number | null;\n /** NUMERIC(10,4). */\n our_ecpm?: number | null;\n our_impressions?: number | null;\n our_requests?: number | null;\n /** NUMERIC(14,6). */\n our_revenue?: number | null;\n provider_name: string;\n /** Adipolo publisher revenue requires a completed vast_requests lifecycle plus latest screens_live source='owned'. ID shape/event alone is insufficient; shared, external, and unjoined bundles are excl... */\n raw_data?: Record<string, unknown> | null;\n report_date: string;\n report_type: \"partner_api\" | \"manual_upload\" | \"email_extract\" | \"dashboard_snapshot\";\n /** NUMERIC(10,4). */\n request_variance?: number | null;\n resolution_notes?: string | null;\n /** NUMERIC(8,4). */\n revenue_variance?: number | null;\n reviewed_at?: string | null;\n reviewed_by?: string | null;\n status: \"pending\" | \"matched\" | \"flagged\" | \"disputed\" | \"resolved\" | \"accepted\";\n /** NUMERIC(10,4). */\n their_ecpm?: number | null;\n /** NUMERIC(6,4). */\n their_fill_rate?: number | null;\n their_impressions?: number | null;\n their_requests?: number | null;\n /** NUMERIC(14,6). */\n their_revenue?: number | null;\n updated_at: string;\n variant_id?: number | null;\n};\n\n/**\n * Optional display behavior applied while registering or re-registering a device.\n */\nexport type PartnerRegistrationDisplaySettings = {\n content_fit?: ContentFitSettings;\n};\n\nexport type PartnerRequirement = {\n /** Stable machine code, e.g. location_unresolvable, location_awaiting_device_gps, enrichment_failed, display_size_missing. */\n code?: string;\n /** Built mechanically as `https://api.trillboards.com/docs/monetization-requirements#<code>`, so the anchor always matches `code`. A bare `docs.` subdomain is deliberately NOT used — it resolves to an unrelated portal SPA rather than documentation — and a CI guard blocks reintroducing it. */\n doc_url?: string;\n /** The request field this concerns (location, venue, display). */\n field?: string;\n /** Human-readable, actionable remediation. */\n message?: string;\n /** Specific reason variant, e.g. city_without_state, no_location_provided, geocode_failed, awaiting_device_gps. */\n reason?: string;\n};\n\n/**\n * Partner-safe subset of device telemetry. Strips MDM compliance,\n * Vertex embeddings, identity-correction internals, partner identifiers,\n * and Mongo `_id` so partners only see what they need to decide\n * whether a device is healthy enough to receive ads.\n *\n */\nexport type PartnerSafeHeartbeatTelemetry = {\n /** Agent lifecycle word, with two disjoint vocabularies depending on the reporting SDK (`sensing`/`camera_failed`/`starting`/`idle` from the Android sensing SDK, `foreground`/`background` from the browser SDK). Absent on the primary `redis_twin` path — the device twin has no such field, so it surfaces only on the `pg_fallback` path. */\n agentStatus?: string | null;\n /** Trillboards agent/SDK version. Never present here on either path: the heartbeat writes it to the device record root, not into the last-heartbeat telemetry blob, and the device twin does not carry it either. It is also nulled at write time for any heartbeat that does not self-identify as a Trillboards SDK. */\n agentVersion?: string | null;\n /** Downlink bandwidth in Mbps. Never present on either path: the write path stores it under `networkMetrics.bandwidthMbps`, a sibling of the last-heartbeat blob this endpoint reads, and the device twin has no such field. */\n bandwidthMbps?: number | null;\n /** Panel brightness on the platform's own scale (0-100 on Tizen/webOS, `null` where no picture-control API exists). Absent on the primary `redis_twin` path — the device twin has no such field, so it can only appear on the `pg_fallback` path and only for the TV agents that emit it. */\n brightness?: number | null;\n /** Free-text connection label. Not present on either path — the write path stores it as `networkMetrics.connectionType`, and while the device twin does carry a `connectiontype`, that is the all-lowercase OpenRTB INTEGER enum, a different quantity under a different key. */\n connectionType?: string | null;\n /** Running count of back-to-back ad failures the player has seen, from the heartbeat's `playback.consecutive_failures`; it is what drives the server's stalled-playback recovery. Absent on the primary `redis_twin` path — the device twin has no such field, so it appears only on the `pg_fallback` path. */\n consecutiveAdFailures?: number | null;\n /** Network Information API `effectiveType` bucket (`slow-2g`/`2g`/`3g`/`4g`) — a latency/throughput classification, not the physical bearer. Stored under `networkMetrics.effectiveType` and carried by neither source object, so it never appears here. */\n effectiveType?: string | null;\n /** Round-trip time in MILLISECONDS (bounded 0-60000 by the read-side schema). Like `bandwidthMbps` it is stored under `networkMetrics.*` and is on neither of this endpoint's two source objects. */\n latencyRtt?: number | null;\n /** Device manufacturer, after the identity-fusion correction pass — so it can differ from what the device reported. Present only on the `redis_twin` path: it is a device-record field, and the `pg_fallback` path returns just the last-heartbeat telemetry blob. */\n make?: string | null;\n /** Device model, after identity-fusion correction. Like `make`, it is a twin-only field and is absent whenever `measurementMetadata.source` is `pg_fallback`. */\n model?: string | null;\n /** Mute state. Absent on the primary `redis_twin` path — the device twin has no such field. On the agents that do report it, `false` is also the fallback used when the power state could not be read at all, so it does not prove a successful read. */\n muted?: boolean | null;\n /** RSSI in dBm, bounded -120..0. Doubly absent: no shipped agent produces it in the first place, and absent on the primary `redis_twin` path — the device twin has no such field. */\n networkStrengthDbm?: number | null;\n /** Declared but NEVER POPULATED on either source path: nothing writes an `onlineStatus` into the last-heartbeat telemetry blob and the device twin has no such field. The equivalent state lives on the screen row (`earner_screens.online_status`), which this endpoint does not read — use `stalenessSeconds` to judge liveness instead. */\n onlineStatus?: \"online\" | \"offline\" | \"standby\" | \"unknown\" | null;\n /** Operating-system family after correction — the Android agents report the full `\"Android <release>\"` string. Twin-only, so absent on the `pg_fallback` path. */\n os?: string | null;\n /** OS version string after correction; the heartbeat collapses the `osv` and `osVersion` request spellings into this one value. Twin-only, so absent on the `pg_fallback` path. */\n osv?: string | null;\n /** Last reported display power (`on`/`standby`/`off`/`unknown`). Absent on the primary `redis_twin` path — the device twin has no such field, so it appears only when `measurementMetadata.source` is `pg_fallback`, which reads the persisted last-heartbeat telemetry blob. */\n powerState?: string | null;\n /** Degrees Celsius from the Android BATTERY thermal sensor (bounded -50..150 by the read schema). Absent on the primary `redis_twin` path — the device twin has no such field, and battery-less CTV hardware never reports it at all, so absence carries no thermal information. */\n temperatureC?: number | null;\n /** User agent, resolved with a three-tier fallback — the corrected device UA, then the stored device UA, then the `User-Agent` header of the heartbeat request that built the twin — so it can reflect the HTTP client rather than the player. Twin-only. */\n ua?: string | null;\n /** Seconds since the DEVICE booted, not since the agent started — it survives an agent restart and counts through deep sleep. Absent on the primary `redis_twin` path — the device twin has no such field, so it surfaces only on the `pg_fallback` path. */\n uptimeSeconds?: number | null;\n /** The browser `document.visibilityState` of the player page (`visible`/`hidden`), sourced from the heartbeat's `content.visibility_state`. One of only two telemetry-derived fields that survive onto the Redis device twin, so it is the one health signal available on BOTH source paths. */\n visibilityState?: string | null;\n /** Speaker volume on the platform's own scale, not a normalised percentage. Absent on the primary `redis_twin` path — the device twin has no such field; only the Tizen and webOS agents ever supply it. */\n volume?: number | null;\n [key: string]: unknown;\n};\n\nexport type PartnerSelfPromoAd = {\n /** The 24-hex advertisement id, minted server-side with `newObjectId()`, and the `ad_id` the schedule and disable endpoints take. Partner self-promo rows carry no `user_mongo_id`, so this id is only resolvable through the partner's own `partner-self-promo` placements — an owner-keyed lookup structurally cannot find it. */\n _id?: string;\n /** Click-through / QR destination; empty string, not null, when it was never set — the PG read coalesces it the same way. */\n campaign_link?: string;\n /** When the advertisement row was written. It is the rotation order for a screen's self-promo pool: the placements read sorts `instant_ad_qr` first, then oldest `created_at` first. */\n createdAt?: string;\n /** Defaults to `name` at create, never null. Note the placements listing trims the embedded advertisement to `_id`, `name`, `video`, `photos`, `status` — this field is absent there. */\n description?: string;\n /** Required at create. It is also copied onto each placement as `advertisement_name`. */\n name?: string;\n /** Image creatives. A JSON create puts `image_url` here as the single element; the multipart route puts the uploaded file here when its MIME type is `image/*`. Empty array for a video-only promo. */\n photos?: Array<string>;\n /** Minted as `approved` — self-promo skips review entirely. The disable endpoint sets it to `deactivated`, and that is the ONLY thing that stops delivery: placements stay `booked` so the disable is idempotent, and every serve-path read has to gate on this field. The listings additionally hide `deleted` and `rejected`. */\n status?: string;\n /** URL of the video creative, or `null` for an image-only promo. This is the field the `trillboards_direct` VAST bridge serves from, which is why the advertisement row is written and awaited BEFORE any placement is created. */\n video?: string;\n /** Measured length of the `video` creative in whole seconds, probed when the ad is created. This is the real media length and is what the served VAST declares as `<Duration>`. `null` means the media could not be measured, in which case the VAST declares the 30 second ad-slot default instead. */\n video_duration_seconds?: number | null;\n};\n\nexport type PartnerSelfPromoCreate = {\n /** Click-through / QR destination. Stored as an empty string, not null, when omitted. */\n campaign_link?: string;\n /** Optional; defaults to `name` rather than staying null. */\n description?: string;\n /** Last day the promo may serve, in each screen's own timezone. Omitting it leaves the placement OPEN-ENDED (`preserveOpenEnd: true`) — it runs until you disable the ad — and leaves `end_time` null rather than 23:59. */\n end_date?: string;\n /** The image creative, stored as a single-element `photos` array. At least one of `image_url` or `video_url` is required (400 `Provide image_url or video_url`); sending both stores both. */\n image_url?: string;\n /** Required — the create is rejected 400 without it. It is also written onto every placement as `advertisement_name`, so it is what appears in delivery bookkeeping. */\n name: string;\n /** Which of YOUR screens the promo is placed on. Accepts a real array, a JSON-encoded array string, or a comma-separated string; the list is intersected with the partner's own non-deactivated screens so a foreign id is dropped silently, but if you send ids and NONE survive the create fails 404. Omitting it entirely creates the advertisement with no placements, so it serves nowhere until you call the schedule endpoint. */\n screen_ids?: Array<string>;\n /** First day the promo may serve, resolved PER SCREEN in that screen's own timezone. A window that is already open lands the placement `active` and fires an immediate `self_promo_active` push to the screen; a future one lands it `scheduled`. Serving times are fixed at 00:00-23:59 local and are not derived from this value. */\n start_date?: string;\n /** The video creative. Its length is PROBED at create time and returned as `video_duration_seconds`, which is what the served VAST declares as `<Duration>`; a spot longer than the 30-second slot also comes back as a non-fatal entry in the response `warnings` array. The probe never blocks the create — on failure the duration stays null and the VAST falls back to a flat 30s. */\n video_url?: string;\n};\n\nexport type PartnerSelfPromoPlacement = {\n /** One entry per `partner-self-promo` placement on that screen, each carrying `allocation_id`, `status`, `start_date`, `end_date` and a TRIMMED `advertisement` (`_id`, `name`, `video`, `photos`, `status` only — no `description`, `campaign_link` or `createdAt`). The underlying read filters out `deactivated`/`deleted`/`rejected` advertisements, so a disabled promo disappears from here even though its placement row still exists. */\n ads?: Array<{\n advertisement?: PartnerSelfPromoAd;\n allocation_id?: string;\n end_date?: string;\n start_date?: string;\n status?: string;\n }>;\n /** Trimmed to `_id` and `name` only. EVERY non-deactivated screen the partner owns appears in this listing, including ones with an empty `ads` array — the response is a full screen roster, not just the placed subset. */\n screen?: {\n _id?: string;\n name?: string;\n };\n};\n\nexport type PartnerStats = {\n /** Recomputed by the daily stats cron as the count of the partner's screens in status `approved`/`active` — an APPROVAL count, not a liveness count. The identically-named field on `GET /v1/partner/analytics` is instead derived live from `online_status`, so the two measure different things. */\n active_devices?: number;\n /** ISO timestamp stamped by the partner impression-record endpoints on each recorded impression. Only those endpoints write it, so it does not move for revenue booked through other rails. */\n last_impression_at?: string;\n /** Integer US cents already reserved by an in-flight payout request. Sourced from the typed `partners.pending_payout_cents` column (the JSONB key was dropped) and subtracted from the available balance so repeat payout requests cannot double-spend. */\n pending_payout_cents?: number;\n /** Read from the `partners.stats` JSONB cache, maintained by signed-delta counters on device register/delete. `GET /v1/partner/analytics` publishes the same field name from a LIVE device count instead, so the two can disagree. */\n total_devices?: number;\n /** Incremented per impression by the partner impression-record path, then OVERWRITTEN by the daily reconcile with `SUM(impressions_count)` over `programmatic_earnings_daily` — two different denominators writing one key, so its meaning depends on when in the cron cycle you read it. */\n total_impressions?: number;\n /** Integer US cents. Recomputed daily as `SUM(earner_revenue) * 100` across ALL of the partner's non-demo `programmatic_earnings_daily` rows, with no payable-source filter and no Net-60 maturity gate — so it can exceed `EarningsResponse.total_lifetime_cents`, which applies the payable filter. */\n total_revenue_cents?: number;\n};\n\n/**\n * One taxi-meter state change reported by the partner SDK's setTripState. (partner, device, trip_id, event_seq) is the event's identity.\n */\nexport type PartnerTripState = {\n /** The host's destination text or zone. */\n destination?: string;\n /** The event's position in the trip. Increases within a trip; a retry re-sends the same value (idempotency key with trip_id, per device). */\n event_seq: number;\n /** Fare so far, in the smallest currency unit. */\n fare_cents?: number;\n /** true while the meter runs (a hired trip). */\n meter_on: boolean;\n /** Riders the host counted. */\n passenger_count?: number;\n /** The host's payment type (stored trimmed and lowercased). */\n payment_type?: string;\n /** When the meter changed state (device clock), within the last 7 days and at most 5 minutes ahead of the server clock. */\n state_at: string;\n /** Tip, in the smallest currency unit. */\n tip_cents?: number;\n /** The host app's trip id. Unique per device, not per partner. */\n trip_id: string;\n};\n\n/**\n * Canonical INT-PK identity for partner variants (e.g. adipolo_lower_floor_tablet). Wave 2 of pristine-state refactor. variant_name TEXT remains as the wire-format alternate-key indefinitely.\n */\nexport type PartnerVariants = {\n archived: boolean;\n created_at: string;\n /** NUMERIC(10,2). */\n daily_budget_cap?: number | null;\n daily_impression_cap?: number | null;\n daily_request_cap?: number | null;\n /** Backfill is 1:N from demand_partner_config.variant_names[]. Rows with NULL demand_partner_id at apply time are silently skipped — deploy-controller previous-tick guard ensures Wave 2 A1 has populat... */\n demand_partner_id: number;\n /** vast_tag_variants JSONB on demand_partner_config still co-exists during transition. Backfill UPDATE pulls display_name from the JSONB if present (no-op today; forward-compatible). */\n display_name?: string | null;\n /** public_alias is the obfuscation alias for partner-API responses (matches partnerAlias.encodeAlias pattern: 'var_' || sha256(variant_name)[0..5]). */\n public_alias: string;\n request_pressure_base_interval_seconds?: number | null;\n /** NUMERIC(12,8). */\n request_pressure_completion_rate_floor?: number | null;\n /** NUMERIC(6,3). */\n request_pressure_confidence_level?: number | null;\n request_pressure_defer_seconds?: number | null;\n /** request_pressure_enabled=false fails open for pressure shaping only. It must not be treated as demand-source enablement. */\n request_pressure_enabled?: boolean | null;\n request_pressure_max_multiplier?: number | null;\n request_pressure_min_pair_requests?: number | null;\n request_pressure_min_source_requests?: number | null;\n request_pressure_probe_interval_seconds?: number | null;\n request_pressure_probe_modulo?: number | null;\n /** NUMERIC(10,4). */\n request_pressure_probe_target_per_minute?: number | null;\n updated_at: string;\n /** feeds_into lists tables that will gain a nullable variant_id INT FK in Wave 2 Stream B. Each is a separate PR — this PR mints the dimension only. */\n variant_id?: string | null;\n /** variant_name TEXT is the wire-format alternate-key (kept forever) — internal logs and partner integrations rely on it. Never propose dropping variant_name. */\n variant_name: string;\n};\n\n/**\n * Per-zone per-day CPM rates from partner APIs. Used for revenue estimation and settled partner-truth allocation when partner_reconciliation_reports does not provide a newer manual/API row. PG only.\n */\nexport type PartnerZoneCpmDaily = {\n created_at: string;\n /** Wave 2 B added demand_partner_id INT + variant_id INT FKs. Legacy partner_key TEXT kept as wire-format alternate-key. Backfill via follow-up Wave 2 B-backfill PR. */\n demand_partner_id?: number | null;\n /** NUMERIC(10,4). */\n ecpm: number;\n fetched_at: string;\n /** NUMERIC(6,4). */\n fill_rate?: number | null;\n /** NUMERIC(14,6). */\n gross_revenue?: number | null;\n id?: string | null;\n impressions?: number | null;\n partner_key: string;\n period_date: string;\n requests?: number | null;\n /** Daily earnings aggregation may use these rows to settle gross revenue, but completed_impressions remains the event-count source for programmatic_earnings_daily.impressions_count. */\n source: string;\n updated_at: string;\n variant_id?: number | null;\n zone_id: string;\n zone_label?: string | null;\n};\n\nexport type PayoutResponse = {\n /** Integer US cents actually reserved: `min(requested, available)`, where available is CONFIRMED earnings minus anything already pending. It must clear the partner's `payout_threshold_cents` (default 2500, i.e. $25.00) or the request is rejected `400`. */\n amount_cents?: number;\n /** Today plus 5 CALENDAR days, computed in the handler with no reference to banking days, the Stripe payout schedule, or whether any transfer exists. It is a display placeholder. */\n estimated_arrival?: string;\n /** Generated in the handler as `po_<epoch_ms>_<random>` and **not persisted anywhere** — no row, no Stripe object. It cannot be used to look the payout up afterwards. */\n payout_id?: string;\n /** Always the literal `processing` on a successful request — the other enum values are never returned by this endpoint. The only side effect is a bump to the partner's `pending_payout_cents` counter; no Stripe transfer is created here. */\n status?: \"pending\" | \"processing\" | \"completed\" | \"failed\";\n /** **Always `null`.** The handler returns a hardcoded null because it creates no transfer; a real transfer id appears only later on the corresponding `payout` row from `GET /earnings/transactions`. */\n stripe_transfer_id?: string | null;\n};\n\nexport type PlaygroundResult = {\n /** Whether the operation is safe for autonomous agents. */\n agent_safe: boolean;\n /** Billing tier: free, metered, or paid. */\n cost_tier: string;\n /** Whether the operation is idempotent. */\n idempotent: boolean;\n /** Object type identifier. */\n object: \"playground_result\";\n operation_id: string;\n /** Declared side effects of the operation. */\n side_effects: Array<string>;\n /** Whether the request body passed validation. */\n valid: boolean;\n /** The parsed/coerced body on success, null on failure. */\n validated_body?: unknown;\n /** Array of validation issues on failure, null on success. */\n validation_errors: Array<PlaygroundValidationError> | null;\n /** The HTTP method and path this operation maps to. */\n would_call: {\n method: string;\n path: string;\n };\n};\n\nexport type PlaygroundValidationError = {\n /** Zod issue code. */\n code: string;\n /** Human-readable validation message. */\n message: string;\n /** Dot-delimited path to the invalid field. */\n path: string;\n};\n\nexport type ProgrammaticSettingsInput = {\n /** Enable programmatic ads */\n enabled?: boolean;\n /** Minimum CPM in dollars */\n floor_price_cpm?: number;\n /** Accepted by this schema but silently discarded. The write path keeps only an explicit whitelist — `enabled`, `provider`, `vast_tag_url`, `timeout_ms`, `programmatic_allocation_percent`, `floor_price_cpm`, `currency`, `dynamic_floor_pricing`, `blocked_advertisers`, `blocked_iab_categories`, `preferred_deal_ids` — and `google_ima` is not in it. A body containing only this key persists nothing and still returns 200. */\n google_ima?: {\n ad_tag_url?: string;\n enabled?: boolean;\n max_duration?: number;\n };\n /** Accepted by this schema but silently discarded — not in the write whitelist, never persisted, and never read back (`header_bidding_settings.prebid` is JSON `null` on every row). A body containing only this key persists nothing and still returns 200. */\n prebid?: {\n bidders?: Array<{\n name?: string;\n params?: Record<string, unknown>;\n }>;\n enabled?: boolean;\n };\n /** Ad source priority */\n priority?: \"direct_first\" | \"programmatic_first\" | \"highest_bid\";\n};\n\nexport type ProgrammaticSettingsResponse = {\n /** The master serving gate `earner_screens.programmatic_enabled`, which is `NOT NULL DEFAULT true` — so it reads `true` for a screen nobody has ever configured — and NOT the legacy `header_bidding_settings.enabled` shadow. Setting it false stops programmatic serving for the screen; the write invalidates the serving caches, and a missed invalidation still drains within the 120s hot-cache TTL. */\n enabled?: boolean;\n /** Minimum clearing price in DOLLARS per thousand impressions (not cents), default `0.50`, accepted range 0.01-100, denominated in the screen's `currency`. It is a floor only: the auction clears at the `max()` of this, any floor rule, the dynamic floor and any matched deal floor, so a low value here does not guarantee cheap fill and can never lower a rule-derived floor. */\n floor_price_cpm?: number;\n /** Never emitted. The response formatter builds the entire body and has no `google_ima` key, and the string appears nowhere on this API's request or response surface — there is no producer. Configure the IMA tag through the screen's `vast_tag_url` / VAST waterfall instead. */\n google_ima?: {\n ad_tag_url?: string;\n enabled?: boolean;\n max_duration?: number;\n };\n /** Constant discriminator `programmatic_settings` (Stripe-style envelope). It is present on the GET, the PUT and the PATCH alike, so it identifies the resource type and tells you nothing about which verb produced the response. */\n object?: string;\n /** Never emitted. The response formatter has no `prebid` key, and the underlying `header_bidding_settings.prebid` is JSON `null` on every stored row with no typed home — nothing populates it on either side. */\n prebid?: {\n bidders?: Array<Record<string, unknown>>;\n enabled?: boolean;\n };\n /** Never emitted, and not settable. The response formatter has no `priority` key and the field is absent from the write whitelist, so `direct_first` / `programmatic_first` / `highest_bid` can neither be stored through this endpoint nor read back from it. */\n priority?: string;\n /** Server-minted per HTTP request in the form `req_<32 hex>`, and also returned in the `X-Request-Id` response header. A client-supplied `X-Request-Id` is echoed back separately as `X-Client-Request-Id` and never becomes this value. Unrelated to `HeaderBiddingSettings.request_id`, which identifies a VAST waterfall attempt. */\n request_id?: string;\n /** Echoed straight back from the path parameter — it is not re-read from the stored row. Must be a 24-character hex Mongo ObjectId; anything else is rejected before any lookup happens. */\n screen_id?: string;\n /** Never emitted — the response formatter has no `stats` key, so `impressions_today`, `fill_rate_percent` and `avg_cpm` have no producer here; use the partner analytics endpoints for delivery numbers. When you do, note that partner-rail impression counts are delivery accounting, not billing: only `completed_impressions` settles revenue. */\n stats?: {\n avg_cpm?: number;\n fill_rate_percent?: number;\n impressions_today?: number;\n };\n /** The SCREEN row's `updated_at`, so any change to the screen moves it — not just a programmatic-settings change. When the row carries no timestamp the server substitutes the current time, so this can never be relied on as 'when these settings last changed' or used as a change-detection cursor. */\n updated_at?: string;\n};\n\nexport type RequestMetadataResponse = {\n duration_ms: number;\n /** Present only when the original request resulted in an error. */\n error?: {\n code: string;\n type: string;\n };\n method: string;\n /** Object type identifier. */\n object: \"request\";\n path: string;\n request_id: string;\n status_code: number;\n timestamp: string;\n};\n\n/**\n * The EFFECTIVE content policy for a screen + its provenance. Whole-layer\n * coalescing: the effective policy comes from exactly ONE layer.\n *\n */\nexport type ResolvedContentPolicy = {\n /** All content_preferences keys + blocked_iab_categories. */\n effective?: Record<string, unknown>;\n /** Always `resolved_content_policy`. This resource is read-only introspection — a \"why is this screen serving what it serves\" preview — and unlike the content-preferences response it IS wrapped in `{ success, data }`. */\n object?: string;\n /** Which single layer supplied the whole `effective` object. `source` is `screen` when the screen's OWN policy is explicit — specifically `content_filtering_enabled === true`, or any of the six content lists non-empty, or a non-empty `programmatic_settings.blocked_iab_categories`; the `allow_*` toggles and the two scalars NEVER make it explicit because they always carry a default. Otherwise it is `group` (lowest `group_id` wins, and `group_id` is present only then), then `network` (the partner's `network_config.default_content_preferences`, used only when it has at least one populated key and merged onto the system baseline), then `default`. */\n provenance?: {\n /** Present only when source=group. */\n group_id?: string;\n source?: \"screen\" | \"group\" | \"network\" | \"default\";\n };\n /** The screen whose effective policy this is, echoed from the path parameter as a string. Ownership is checked first, so a screen you do not own returns 404 rather than an empty policy. */\n screen_id?: string;\n};\n\n/**\n * The EFFECTIVE sensing view for a screen + its provenance (screen\n * deployment ?? group sensing config ?? default).\n *\n */\nexport type ResolvedSensingConfig = {\n /** The profile scalars actually in force after resolution. When the winning layer is a group, the base profile is hydrated first and only the group's NON-null overrides are laid over it, so this is always a complete view rather than a sparse patch. */\n effective?: {\n capture_interval_ms?: number | null;\n estimated_fps_impact?: number | null;\n min_confidence?: number | null;\n profile_name?: string | null;\n report_interval_ms?: number | null;\n };\n /** Stripe-style type discriminator; always the literal `resolved_sensing_config`, so it is safe to switch on. */\n object?: string;\n /** Which layer won: `screen` (the screen's own active deployment), `group_default` (a group sensing config, with `group_id` naming it), or `default` (nothing bound). Resolution is whole-layer — `effective` never mixes values from two sources. */\n provenance?: {\n /** Present only when source=group_default. */\n group_id?: string;\n source?: \"screen\" | \"group_default\" | \"default\";\n };\n /** The screen's Mongo id — the same identifier the route surfaces call `screenMongoId`. Echoes the path parameter. */\n screen_id?: string;\n};\n\n/**\n * Permission flags for each role. Owners have all permissions,\n * admins have operational permissions, viewers have read-only access.\n *\n */\nexport type RolePermissions = {\n /** Can configure webhooks */\n configure_webhooks?: boolean;\n /** Can edit screen settings */\n edit_screens?: boolean;\n /** Can invite/remove team members */\n manage_team?: boolean;\n /** Can request payouts */\n request_payouts?: boolean;\n /** Can rotate API keys */\n rotate_api_keys?: boolean;\n /** Can view impression analytics */\n view_analytics?: boolean;\n /** Can view earnings/revenue */\n view_earnings?: boolean;\n /** Can view impression data */\n view_impressions?: boolean;\n /** Can view payout history */\n view_payouts?: boolean;\n /** Can view online/offline status */\n view_screen_status?: boolean;\n /** Can view screen list */\n view_screens?: boolean;\n};\n\nexport type SDKConfig = {\n /** Seconds between ads */\n ad_interval?: number;\n /** The only `sdk_config` key that changes what is SERVED: if it contains `image`, the server stops substituting a transcoded MP4 for an image creative on `lite_sdk`/`full_sdk` tiers, because you are declaring the client can render images itself. Frozen at the quick-start default `[\"video\", \"image\"]` — there is no partner-facing endpoint that writes `sdk_config`. */\n allowed_ad_types?: Array<string>;\n /** Maximum number of ads the `trillboards-connect` client retains for offline playback (`cacheAdsOffline`). Client-side hint only — the server never reads it — and it is frozen at the quick-start default of 10 because no partner-facing endpoint writes `sdk_config`. */\n cache_size?: number;\n /** Seconds to display image ads */\n image_duration?: number;\n /** Fleet-wide hourly ad cap echoed as `settings.max_ads_per_hour` in the `/ads` response, but only as the FALLBACK behind `network_config.ad_delivery.max_ads_per_hour` — that is the writable control, and this one is frozen at the quick-start default of 20 because no partner-facing endpoint writes `sdk_config`. */\n max_ads_per_hour?: number;\n /** Label the lite SDK paints on the tap-to-continue overlay; when absent the SDK falls back to the literal `TAP TO CONTINUE`, which is also the quick-start default stored for every partner. Frozen — there is no partner-facing endpoint that writes `sdk_config`. */\n overlay_button_text?: string;\n /** Has no reader anywhere in the server or the shipped SDKs — the `settings.sound_enabled` a player actually obeys is resolved per screen from `earner_screens.sound_enabled` / display preferences, not from this partner-level value. Frozen at the quick-start default `false`. */\n sound_enabled?: boolean;\n};\n\n/**\n * A partner screen — the client-facing projection of a registered\n * device+screen pair (`@trillboards/api-client` `Screen`). The screens\n * resource reads the partner device endpoints.\n *\n */\nexport type Screen = {\n /** City resolved at registration by reverse/forward geocoding or taken from your own `location.city`, with placeholder strings (\"City\", \"Vending Machine Location\") coalesced to null rather than stored. The screens resource reads the partner device endpoints, whose payload carries this as `location.city`. */\n city?: string;\n /** Country of the screen, taken from the enriched screen row with a fallback to the device row. The device row DEFAULTS this to `\"US\"` when registration could not resolve a country, so it is effectively never null and a `US` value can mean \"unresolved\". The screens resource reads the partner device endpoints, whose payload carries this as `location.country`. */\n country?: string;\n /** Creation time of the DEVICE row backing the screen, not of the screen record itself — the screens resource is a projection of the partner device endpoints, and the screen can be created later than its device. */\n created_at: string;\n /** The paired device identifier. */\n device_id?: string;\n /** Player embed URL for the screen. */\n embed_url?: string;\n /** Friendly screen/venue name. */\n name: string;\n /** Display resolution label (e.g. \"1920x1080\") when known. */\n resolution?: string;\n /** Stable screen identifier. */\n screen_id: string;\n /** Screen lifecycle status (e.g. pending, active, offline, suspended). */\n status: string;\n /** IANA zone, and it is ONLY ever what you supplied as `location.timezone` at registration — nothing derives it from the resolved coordinates, so a large share of screens carry null. The screens resource reads the partner device endpoints, whose payload carries this as `location.timezone`, and it is the one geo field read device-first rather than screen-first. */\n timezone?: string;\n /** `earner_screen_devices.updated_at` of the backing device, which the device-mirror write buffer stamps `NOW()` on every flush including heartbeats. It moves continuously on a live screen, so it cannot be used to detect a configuration change. */\n updated_at: string;\n /** Granular OpenOOH venue type of the screen's location. */\n venue_type?: string;\n};\n\nexport type ScreenAnalyticsResponse = {\n /** `total` is deduped, GIVT-passed `completed_impressions` over the period. `daily_average` divides by the number of days that RETURNED A ROW, not by the period length, so zero-impression days do not dilute it. `trend_percent` compares the second half of the returned rows against the first half and is `0` whenever the earlier half is zero. */\n impressions?: {\n daily_average?: number;\n total?: number;\n /** Percentage change compared to previous period */\n trend_percent?: number;\n };\n /** `days` is 7, 30 or 90; any other `?period=` value silently becomes 7. `start` is midnight (API-server local time) that many days back and `end` is the moment the request was served, so the window is not a whole number of days. */\n period?: {\n days?: number;\n end?: string;\n start?: string;\n };\n /** `location` is a `City, State` string or `null` when either part is missing. `online_status` comes from the live heartbeat mirror and is `online`/`offline`/`unknown`, where `unknown` means the screen has never heartbeated — it is not a synonym for offline. `status` is the separate approval state. */\n screen?: {\n id?: string;\n location?: string;\n name?: string;\n online_status?: string;\n status?: string;\n };\n /** One entry per day that had impressions — days with none are absent, not zero-filled, which is why `daily_average` and this array can disagree with the period length. **`avg_duration_ms` is always `0`**: it is a hardcoded literal in both the ClickHouse query and the Postgres fallback. */\n timeline?: Array<{\n avg_duration_ms?: number;\n date?: string;\n impressions?: number;\n }>;\n};\n\n/**\n * A reusable, named, partner-scoped set of screens that analytics,\n * moderation policy, sensing/model deployment, fleet commands, and\n * sensing routes can target. Members are always a subset of the\n * partner's own screens.\n *\n */\nexport type ScreenGroup = {\n created_at?: string;\n /** Free-text note. Returned as an explicit `null` rather than omitted when unset, and an empty string supplied at create is stored as `null`. A PATCH writes only the keys you actually send, so omitting it preserves the current value. */\n description?: string | null;\n /** Screen group id: the literal prefix `sg_` followed by a v4 UUID (`pgRepo/screenGroups.js` mints `sg_${crypto.randomUUID()}`). Treat it as an opaque string; do not parse the UUID half. */\n id?: string;\n /** Required, non-empty label. It is NOT unique and has no length constraint in the database, so two of your groups may share a name; `id` (`sg_<uuid>`) is the only stable handle. */\n name?: string;\n /** Constant string `screen_group` — the Stripe-style type discriminator. It is present on every group payload, including inside a webhook's `data`, so a single handler can switch on it. */\n object?: string;\n /** Distinct member screens (COUNT and member list share one junction — they cannot diverge). */\n screen_count?: number;\n /** Bumped by membership changes as well as metadata edits — `addMembers`, `removeMembers` and the soft delete all set `updated_at = NOW()` on the group row — so it is a change marker for the group INCLUDING its member set, not just its name and description. */\n updated_at?: string;\n};\n\n/**\n * The typed content/moderation policy a screen group carries (Phase 4 live\n * policy). `null` when the group has no policy of its own. Member screens\n * inherit it at read/serve time when they have no explicit own policy\n * (screen ?? group ?? partner network default ?? system default). Mirrors\n * the per-screen content_preferences keys 1:1; blocked_iab_categories is\n * first-class here (the auction bcat).\n *\n */\nexport type ScreenGroupContentPolicy = {\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_adult_content`. A `false` also adds `IAB25` plus `IAB25-2`..`IAB25-5` to the `bcat` and `sexually_suggestive`/`sexual_health`/`sensationalism` to the GAM `excl_cat`; unset falls back to the platform default of `false`. */\n allow_adult_content?: boolean;\n /** Rejects a direct ad only when set to `false` AND the ad's classification carries `contains_alcohol`. A `false` also adds `IAB8-5`/`IAB8-18` to the outgoing OpenRTB `bcat` and `alcohol` to the GAM `excl_cat`; when the key is unset that list-building step uses the platform default, which for alcohol is `true` (not blocked). */\n allow_alcohol?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_gambling`. A `false` also adds `IAB9-7` to the `bcat` and `gambling`/`online_gambling` to the GAM `excl_cat`; unset falls back to the platform default of `false`. */\n allow_gambling?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_political`. A `false` also adds `IAB11-4` to the `bcat` and `politics` to the GAM `excl_cat`; unset falls back to the platform default of `true` (allowed). */\n allow_political?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_profanity`. A `false` also adds `IAB25-2` to the `bcat` and `sensationalism` to the GAM `excl_cat`; unset falls back to the platform default of `false`. */\n allow_profanity?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_religious`. A `false` also adds `IAB23` to the `bcat` and `religion` to the GAM `excl_cat`; unset falls back to the platform default of `true` (allowed). */\n allow_religious?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_tobacco`. A `false` also adds `IAB25-6` to the `bcat` and `drugs_tobacco` to the GAM `excl_cat`; unset falls back to the platform default of `false`, so tobacco is blocked on the programmatic side even when you never configure it. */\n allow_tobacco?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_violence`. A `false` also adds `IAB25-3`/`IAB25-4` to the `bcat` and `violence`/`military_conflict` to the GAM `excl_cat`; unset falls back to the platform default of `false`. */\n allow_violence?: boolean;\n /** Consulted only when non-empty, and matched against the ad's typed `iab_categories` with `categoriesMatch`, so IAB codes and human-readable names both work (categories are inferred from subjects/keywords when the ad carries none). Under `lenient` and `permissive` a non-match is allowed through anyway, which makes the list inert at those levels; it also adds +10 per match to the content match score. */\n allowed_categories?: Array<string>;\n /** A SOFT preference, not a filter: it never rejects anything. Matching keywords add +3 each to the content match score used for prioritisation, and the approval path explicitly skips this list. */\n allowed_keywords?: Array<string>;\n /** Matched by case-insensitive SUBSTRING containment against the ad's `moderation.analysis.subjects`, not equality. Consulted only when non-empty and inert under `lenient`/`permissive`, like `allowed_categories`; it also adds +5 per match to the content match score. */\n allowed_subjects?: Array<string>;\n /** When `true` and the ad's classification says `is_known_brand`, approval returns IMMEDIATELY — the `blocked_categories`, `blocked_subjects`, `blocked_keywords` and every `allowed_*` check below it never run. The `allow_*` toggles and the age-rating check sit above it and still apply. */\n auto_approve_known_brands?: boolean;\n /** Same matching as `allowed_categories` (IAB code or human-readable name, via `categoriesMatch`), but a single hit rejects the ad at every `preference_level`. The rejection reason reports the human-readable name rather than the code you stored. */\n blocked_categories?: Array<string>;\n /** The auction `bcat` sent on OpenRTB bid requests for member screens. It is NOT part of `content_preferences`: on an individual screen the same list lives on `programmatic_settings.blocked_iab_categories`, and a non-empty one there is one of the three conditions that makes the screen layer explicit and bypasses this group policy entirely. */\n blocked_iab_categories?: Array<string>;\n /** The keyword list that DOES block, unlike `allowed_keywords`. Case-insensitive substring containment against the ad's `moderation.analysis.keywords`, so a short entry matches inside longer words. */\n blocked_keywords?: Array<string>;\n /** Case-insensitive substring containment against the ad's `moderation.analysis.subjects` — `bar` therefore also blocks `barbecue`, so use whole distinctive words. A hit is a hard reject. */\n blocked_subjects?: Array<string>;\n /** When `false`, direct-ad moderation short-circuits and every ad is auto-approved for member screens. It does NOT disable brand safety on the programmatic side — the `allow_*` toggles are still translated into the OpenRTB `bcat` and GAM `excl_cat` regardless of this flag. */\n content_filtering_enabled?: boolean;\n /** The group this row belongs to. It is also the tiebreak: a screen that is a member of more than one policy-bearing group inherits from the LOWEST `group_id`, so a screen should be in at most one. */\n group_id?: string;\n /** Highest ad classification a member screen accepts. `G` is deliberately NOT the strictest setting — the matcher silently rewrites it to `PG-13` for backwards compatibility — and `preference_level` then adds its buffer on top, so `lenient` with `PG-13` actually admits `R`. */\n max_age_rating?: \"G\" | \"PG\" | \"PG-13\" | \"R\" | \"NC-17\";\n /** Always the literal `screen_group_content_policy`. The policy is returned as `data.content_policy`, and a group carrying no policy of its own returns a bare `null` there — so on that path there is no `object` to branch on at all. */\n object?: string;\n /** Selects a strictness profile, not a label. It sets the age-rating buffer (`strict`/`moderate` 0, `lenient` +1, `permissive` +2 notches above `max_age_rating`), whether an ad with no AI classification is auto-approved (`lenient`/`permissive` yes; `strict`/`moderate` reject and queue re-moderation), and how the `allowed_*` lists behave on a non-match — `strict` rejects, `moderate` lets common business categories through, `lenient`/`permissive` make them inert. */\n preference_level?: \"strict\" | \"moderate\" | \"lenient\" | \"permissive\";\n /** ISO-8601 timestamp of the last write to this policy row, or `null` when the row has none. Member screens read the resolved policy through a screen-keyed Redis cache that a policy, membership or group-delete write busts, so an edit here reaches serving immediately rather than after the 60-second TTL. */\n updated_at?: string;\n} | null;\n\n/**\n * Partial, non-clobbering — only the fields present are written. Same keys\n * the per-screen content-preferences PUT accepts, plus blocked_iab_categories.\n *\n */\nexport type ScreenGroupContentPolicyInput = {\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB25` and `IAB25-2`..`IAB25-5` to the member screens' `bcat`; unset already blocks it via the platform default. */\n allow_adult_content?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` both rejects classified alcohol creatives on member screens and adds `IAB8-5`/`IAB8-18` to their outgoing `bcat`. */\n allow_alcohol?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB9-7` to the member screens' `bcat`; unset already blocks gambling programmatically via the platform default. */\n allow_gambling?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB11-4` to the member screens' `bcat`; the platform default here is `true`, so political ads run unless you set this. */\n allow_political?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB25-2` to the member screens' `bcat`; unset already blocks it via the platform default. */\n allow_profanity?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB23` to the member screens' `bcat`; the platform default here is `true`, so religious ads run unless you set this. */\n allow_religious?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB25-6` to the member screens' `bcat`; leaving it unset already blocks tobacco programmatically via the platform brand-safety default. */\n allow_tobacco?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB25-3`/`IAB25-4` to the member screens' `bcat`; unset already blocks it via the platform default. */\n allow_violence?: boolean;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and the allow/block pair is conflict-checked case-insensitively against this patch MERGED onto the stored row, so it can 400 with `conflicting_lists` on a term you set in an earlier request. */\n allowed_categories?: Array<string>;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and it is conflict-checked against `blocked_keywords`, even though at serve time this list never rejects anything — it only contributes to the content match score. */\n allowed_keywords?: Array<string>;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and the Subjects allow/block pair is conflict-checked against the stored row merged with this patch. Note these are matched by substring at serve time, not equality. */\n allowed_subjects?: Array<string>;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Setting it `true` makes a known-brand ad bypass every category, subject and keyword check on member screens, so treat it as a widening, not a convenience. */\n auto_approve_known_brands?: boolean;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and blocking a category an earlier PUT allowed fails with `conflicting_lists` even though this request names only one side of the pair. */\n blocked_categories?: Array<string>;\n /** Array of strings capped at 100 by a DIFFERENT constant from the other lists (`PROGRAMMATIC_SETTINGS_LIMITS.max_blocked_iab_categories`), reported as `blocked_iab_categories_too_large`. It is not conflict-checked, because there is no `allowed_iab_categories` counterpart. */\n blocked_iab_categories?: Array<string>;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and unlike `allowed_keywords` this one really does reject, by case-insensitive substring match against the ad's analysed keywords. */\n blocked_keywords?: Array<string>;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and it is a hard reject at serve time, matched by case-insensitive substring — so a short entry blocks more than you intend. */\n blocked_subjects?: Array<string>;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Omitting the key leaves the stored value untouched; there is no way to reset a key back to \"inherit\". */\n content_filtering_enabled?: boolean;\n /** One of `G`, `PG`, `PG-13`, `R`, `NC-17`; anything else is `invalid_age_rating`. Writing `G` does not give you the strictest policy — the matcher later treats `G` as `PG-13`. */\n max_age_rating?: \"G\" | \"PG\" | \"PG-13\" | \"R\" | \"NC-17\";\n /** One of `strict`, `moderate`, `lenient`, `permissive`; anything else is rejected with `invalid_preference_level`. Validated against the same constant the per-screen content-preferences PUT uses, so the two surfaces cannot drift. */\n preference_level?: \"strict\" | \"moderate\" | \"lenient\" | \"permissive\";\n};\n\n/**\n * Webhook body for a Screen Group lifecycle event (`screen_group.created` | `updated` |\n * `deleted` | `member.added` | `member.removed` | `content_policy.updated` |\n * `sensing_config.updated`). Standard `{ id, event, timestamp, data }` envelope: `id` (`evt_…`)\n * is the idempotency key, and `data` is the Stripe-style `screen_group` /\n * `screen_group_content_policy` / `screen_group_sensing_config` object the REST API returns.\n * The event type is also in the `X-Trillboards-Event` header — verify the compound\n * `X-Trillboards-Signature` over the raw body first.\n *\n */\nexport type ScreenGroupEvent = {\n /** The screen_group / content-policy / sensing-config object. */\n data?: Record<string, unknown>;\n /** The event type, also sent in the `X-Trillboards-Event` header and mirrored to the SSE stream at `GET /v1/partner/events/stream`. This whole event family is gated by the `SCREEN_GROUP_EVENTS_ENABLED` runtime flag, which is OFF by default — when off, nothing is enqueued at all. */\n event?: \"screen_group.created\" | \"screen_group.updated\" | \"screen_group.deleted\" | \"screen_group.member.added\" | \"screen_group.member.removed\" | \"screen_group.content_policy.updated\" | \"screen_group.sensing_config.updated\";\n /** `evt_<base36 epoch ms>_<12 hex>`, minted once per occurrence. Use it as the idempotency key: a retried delivery of the same occurrence reuses it, but the same id is also sent to EVERY endpoint you have subscribed to that event, so dedupe per (endpoint, id). It is not the server-side delivery audit id and cannot be looked up. */\n id?: string;\n /** Stamped when the server ENQUEUED the delivery, not when it was sent, so a retried delivery still carries the original value and can trail wall-clock by the retry backoff. It is not the resource's own `updated_at`. */\n timestamp?: string;\n};\n\n/**\n * A screen group's sensing profile binding (profile_name) + typed\n * performance overrides. `null` when the group has none. Member screens\n * inherit via the sensing resolver's group step when they have no\n * screen-specific deployment.\n *\n */\nexport type ScreenGroupSensingConfig = {\n /** Edge frame-capture cadence overrides pushed to member screens (SensingConfig.capture keys, milliseconds). Group wins over the profile default per-key when set. */\n capture?: {\n faceTriggeredIntervalMs?: number | null;\n minCaptureIntervalMs?: number | null;\n periodicIntervalMs?: number | null;\n vehicleTriggeredIntervalMs?: number | null;\n } | null;\n /** Group override of the profile's declared frame-capture cadence in milliseconds; `null` inherits the base profile's value. Treat it as advisory - this legacy scalar does not reach the device capture loop (the edge `FrameCaptureManager` reads the nested `capture` block), and observed cadence was measured at ~12x the declared value against a fleet floor of one analysis per 30 s. */\n capture_interval_ms?: number | null;\n /** When `false` the whole group step is a no-op and member screens fall straight through to the next layer of the resolver — the row is retained but contributes nothing, which is how you disable group sensing without losing the overrides. */\n enabled?: boolean;\n /** Advisory workload cost of the profile, normally derived server-side as the sum of the selected models' registry `fps_cost` (built-ins run 5.5-12.0). It is display metadata only — no device or pipeline code reads it, so overriding it throttles nothing. */\n estimated_fps_impact?: number | null;\n /** The `sg_`-prefixed Screen Group this config belongs to; echoes the `groupId` path parameter. */\n group_id?: string;\n /** Detection-confidence floor in 0..1, overlaid onto the resolved profile's `thresholds.min_confidence`; built-in profiles ship 0.35-0.45. `null` inherits the base profile's value. */\n min_confidence?: number | null;\n /** Stripe-style type discriminator; always the literal `screen_group_sensing_config`, so it is safe to switch on. */\n object?: string;\n /** The sensing profile members inherit, bound by NAME (a builtin or an existing custom profile) rather than by `profile_id`, because `setDeployment` clones a fresh `profile_id` per screen on every redeploy. `null` means the group binds no profile. */\n profile_name?: string | null;\n /** Group override of how often the device batches observations back to the server, in milliseconds; `null` inherits the base profile's value. Built-in profiles ship 15000-30000. */\n report_interval_ms?: number | null;\n /** When this group's sensing config row was last written. The write also busts the resolver caches for the members as they stood at that instant and emits a `screen_group.sensing_config.updated` webhook, so a screen added to the group afterwards picks the config up on its own next cache miss instead. */\n updated_at?: string;\n /** Per-screen cloud-vision budget for member screens. screen_hourly_pace wins when both are set; otherwise derived from screen_daily_calls. null = no group budget (fleet keeps platform-default caps). */\n vision_budget?: {\n screen_daily_calls?: number | null;\n screen_hourly_pace?: number | null;\n } | null;\n} | null;\n\n/**\n * Partial, non-clobbering. A null override means \"inherit that field from\n * the base profile\". profile_name must resolve to a builtin or an existing\n * custom profile (else 400).\n *\n */\nexport type ScreenGroupSensingConfigInput = {\n /** Edge frame-capture cadence block (milliseconds); null clears it. Unknown keys are rejected (400). */\n capture?: {\n faceTriggeredIntervalMs?: number | null;\n minCaptureIntervalMs?: number | null;\n periodicIntervalMs?: number | null;\n vehicleTriggeredIntervalMs?: number | null;\n } | null;\n /** Frame-capture cadence override in milliseconds (100..3600000); `null` means inherit from the base profile. Advisory in practice: this legacy scalar does not reach the device capture loop (the edge reads the nested `capture` block), the fleet floors at one analysis per 30 s, and observed cadence was measured at ~12x the declared value. */\n capture_interval_ms?: number | null;\n /** Set `false` to make the group step a no-op — members fall through to the next resolver layer while the overrides stay stored, so this is the reversible way to switch group sensing off. */\n enabled?: boolean;\n /** Advisory workload-cost number (must be >= 0); `null` inherits the profile's own value, which the server derives from the selected models' registry `fps_cost`. Purely descriptive — setting it changes what readback and operator surfaces display and throttles nothing. */\n estimated_fps_impact?: number | null;\n /** Detection-confidence floor in 0..1 written into the resolved profile's `thresholds.min_confidence`; `null` inherits the base profile's value (built-ins are 0.35-0.45). */\n min_confidence?: number | null;\n /** Bind the group to a builtin or existing custom profile BY NAME; a name that resolves to neither is a 400. `null` clears the binding. Never a `profile_id` — that is re-minted per screen on each redeploy. */\n profile_name?: string | null;\n /** Observation-reporting cadence override in milliseconds (100..3600000); `null` means inherit from the base profile. */\n report_interval_ms?: number | null;\n /** Per-screen cloud-vision budget block; null clears it. Unknown keys are rejected (400). */\n vision_budget?: {\n screen_daily_calls?: number | null;\n screen_hourly_pace?: number | null;\n } | null;\n};\n\n/**\n * High-level sensing workload intent. Hardware tier and device telemetry are read-only server facts.\n */\nexport type SensingPerformanceMode = \"auto\" | \"presence_only\" | \"demographics\" | \"advanced\";\n\n/**\n * A Sense Anything Route — a (condition → channel actions) alert/automation rule.\n */\nexport type SensingRoute = {\n /** Every channel action fired when the route matches; 1 to 10 per route, each with its own `enabled` flag so one can be muted without deleting the route. `webhook` is the only channel with no external side effect — `whatsapp`, `sms`, `email` and `slack` deliver a real message to a real person, so point a test route at `webhook`. */\n actions?: Array<SensingRouteAction>;\n /** One bit per weekday (127 = always). */\n activeDaysMask?: number;\n /** Inclusive end of the daily active window, in minutes past local midnight in `activeTz` (0..1439). When it is LESS than `activeStartMinute` the window is read as overnight — 1200 to 360 means 20:00 through 06:00 — rather than as an empty range. */\n activeEndMinute?: number | null;\n /** Inclusive start of the daily active window, in minutes past local midnight in `activeTz` (0..1439; 1200 = 20:00). Ignored unless `activeTz` is set, and a `null` on either end makes the window whole-day. */\n activeStartMinute?: number | null;\n /** IANA zone name (e.g. `America/New_York`) the active window is evaluated in, trimmed to 64 characters. `null` disables the ENTIRE active-window check — `activeStartMinute`, `activeEndMinute` and `activeDaysMask` do nothing without it — and a zone the runtime cannot parse fails OPEN (always active) rather than silently dropping alerts. */\n activeTz?: string | null;\n /** `null` on a `count`-metric digest route (there is nothing to aggregate over). On an `avg`/`max` digest it carries only `field`, with `op` and `value` absent — only a `threshold`-mode route has a complete condition. */\n condition?: SensingRouteCondition | null;\n createdAt?: string;\n /** Minimum gap in seconds between alerts for the same (route, screen) episode; clamped to 0..86400 and defaulting to 300. It also caps the falling-edge clear window (60 s maximum), so a shorter debounce re-arms the route sooner as well as spacing alerts more tightly. */\n debounceSeconds?: number;\n /** Scheduled-aggregate configuration; `null` on `threshold` routes. `nextRunAt` is a server-held cursor the digest cron advances after every send, so it moves on its own without any edit from you. */\n digest?: {\n intervalMinutes?: number;\n metric?: \"count\" | \"avg\" | \"max\";\n nextRunAt?: string;\n windowMinutes?: number;\n } | null;\n /** Whether the evaluator considers this route at all. A disabled route keeps its condition, actions and screen set and is simply skipped by every hot-path read; deleting a route also sets this `false` (rows are soft-deleted, never removed). */\n enabled?: boolean;\n /** `threshold` evaluates `condition` against each incoming observation. `digest` ignores the condition and instead sends a scheduled aggregate driven by the `digest.nextRunAt` cursor; focus regions and explicit review timing are rejected on digest routes. */\n mode?: \"threshold\" | \"digest\";\n /** Operator-supplied label, trimmed and capped at 200 characters. It is delivered verbatim as `route_name` on every fire but is not the alert text — the human-readable message is composed separately from the screen's sensing intent. */\n name?: string;\n /** The owning principal's id, compared against your credential on every read and write — a mismatch answers 404 (existence-hidden), never 403. Equal to your partner id for routes created here. */\n ownerId?: string;\n /** Which principal created the route: `partner` for anything created through this API, `earner_user` for a route the screen's own operator created in the earner portal. The Partner API only ever lists or edits `partner` routes, but an `earner_user` route on your screen still delivers webhooks to you. */\n ownerKind?: string;\n /** The partner the route was created under. Equal to `ownerId` for partner-created routes and `null` for a route an earner created in the portal, so it is not a reliable join key for the screen's partner. */\n partnerId?: string;\n /** Binds the route to a sensing profile BY NAME, never by `profile_id` — redeploying a profile mints a fresh `profile_id` per screen, which would orphan the binding. `null` means unbound; when set, the route stays dormant on any screen whose active profile has a different name. */\n profileName?: string | null;\n reviewTimingMode?: SensingRouteReviewTimingMode;\n reviewWaitSeconds?: SensingRouteReviewWaitSeconds;\n /** Server-minted immutable id: the literal prefix `rt_` followed by a UUID. Webhook consumers should key on this rather than `route_name`, which the owner can rename at any time. */\n routeId?: string;\n /** Per-camera areas, at most one entry per screen. The legacy cloud path converts geometry into prompt text and is not geometrically enforced (measured 2026-08-25 on gemini-3.1-flash-lite). The retained local connector masks actual pixels separately for each declared scope; original unmasked JPEGs remain alert evidence. Retained installation and model accuracy require separate qualification. */\n screenFocusRegions?: Array<SensingRouteScreenFocusRegion>;\n /** The screens this route watches — deduped and capped at 250. For a group-targeted route these are the group's LIVE members resolved at read time, not a stored set, so the list can change between two GETs without any edit. */\n screenMongoIds?: Array<string>;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainMinSamples?: number;\n /** How long the condition must hold before the FIRST alert, in seconds (600 = ten minutes). Omit to alert on the first true reading. Cadence-independent, because the evaluator compares timestamps - correct however often the screen reports, including when that rate changes while the condition holds. */\n sustainSeconds?: number | null;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainWindowSeconds?: number;\n /** The sg_ Screen Group this route targets. When set, the route fires on the group's LIVE members (screenMongoIds reflects the resolved members at read time) and no explicit screens are stored. */\n targetGroupId?: string | null;\n /** Last write to the route row — NOT a configuration-change stamp. The digest cron bumps it every time it advances a digest route's next-run cursor, and a soft delete bumps it too, so it moves on rows nobody edited. */\n updatedAt?: string;\n};\n\n/**\n * One channel action fired when the route matches.\n */\nexport type SensingRouteAction = {\n /** Attach the evidence frame/clip (people-facing channels). */\n attachClip?: boolean;\n /** Selects a strict per-channel shape: `webhook` requires `webhookId`, `whatsapp`/`sms` require `recipientE164`, `email` requires `recipientEmail`, `slack` requires `slackChannelId`, and `clip` needs no destination. Any other destination field on the action is rejected, and `webhook` is the only channel that does not deliver to a person — use it for test routes. */\n channel: \"webhook\" | \"whatsapp\" | \"sms\" | \"slack\" | \"email\" | \"clip\";\n /** E.164 phone number (channel=whatsapp|sms). */\n recipientE164?: string;\n /** Recipient email (channel=email). */\n recipientEmail?: string | null;\n /** Slack channel id (channel=slack). */\n slackChannelId?: string | null;\n /** Message template name (managed WhatsApp/SMS). */\n templateName?: string;\n /** Target webhook endpoint id (channel=webhook). */\n webhookId?: string | null;\n};\n\n/**\n * A threshold condition — the route fires when the sensed field crosses the value.\n */\nexport type SensingRouteCondition = {\n /** The sensed field key (snake_case) to evaluate. */\n field: string;\n /** How the observed value is compared with `value`. `gt`/`gte`/`lt`/`lte` match ONLY when both sides parse as finite numbers; `eq`/`neq` fall back to strict equality for booleans and non-numeric strings. A missing or null observation never matches — including `neq`, which does not fire on absence. */\n op: \"gt\" | \"gte\" | \"lt\" | \"lte\" | \"eq\" | \"neq\";\n /** The threshold value (numeric, string, or boolean depending on the field). */\n value?: number | string | boolean;\n};\n\n/**\n * Threshold-alert timing. NULL preserves legacy behavior; immediate adds no check delay; wait_recheck requires distinct first/follow-up positive images at least reviewWaitSeconds apart on capture time, not uninterrupted activity or message grouping.\n */\nexport type SensingRouteReviewTimingMode = \"immediate\" | \"wait_recheck\" | null | null;\n\n/**\n * Whole seconds before a fresh follow-up check. Required only for wait_recheck; must be NULL for immediate or unspecified timing. These two settings must be updated together.\n */\nexport type SensingRouteReviewWaitSeconds = number | null;\n\n/**\n * A normalized camera area used only by this alert. The rectangle is the compatibility bounding box; optional ordered points define the exact lasso perimeter. Omitted cameras use the whole view.\n */\nexport type SensingRouteScreenFocusRegion = {\n /** Rectangle height as a fraction of frame height, at least 0.05. When `points` is supplied this is DERIVED from the lasso's bounding box. Note the region is only ever sent to the model as prompt text and is not geometrically enforced — measured 2026-08-25, both shapes leaked 100% at every anchor, so a focused alert reacts to the whole frame. */\n height: number;\n /** Ordered non-self-intersecting lasso vertices, normalized to a 0.01 coordinate grid so the saved perimeter is represented exactly in model instructions. The perimeter closes from the last point to the first. */\n points?: Array<{\n x: number;\n y: number;\n }>;\n /** Which of the route's cameras this area applies to. It must appear in the route's explicit `screenMongoIds` and may appear at most once across the array, or the write is a 400; cameras with no entry are watched whole-view. */\n screenMongoId: string;\n /** Rectangle width as a fraction of frame width, at least 0.05. When `points` is supplied this is DERIVED from the lasso's bounding box rather than read from the request, so older readers keep a safe approximation of the polygon. */\n width: number;\n /** Left edge of the bounding rectangle as a fraction of frame width, origin top-left. Stored rounded to 4 decimal places, and capped at 0.95 because `width` has a 0.05 minimum and `x + width` may not exceed 1. */\n x: number;\n /** Top edge of the bounding rectangle as a fraction of frame height, origin top-left. Stored rounded to 4 decimal places, and capped at 0.95 because `height` has a 0.05 minimum and `y + height` may not exceed 1. */\n y: number;\n};\n\nexport type SensingSettingsResponse = {\n /** Summary of the profile row currently deployed and active on this screen (id, name, cadences and derived capabilities). `null` when the screen has never had a profile deployed, in which case the response's mode fields describe the default rather than anything running. */\n active_profile?: {\n capabilities?: Array<string>;\n capture_interval_ms?: number | null;\n profile_id?: string | null;\n profile_name?: string | null;\n report_interval_ms?: number | null;\n } | null;\n desired_mode?: SensingPerformanceMode;\n /** Read-only latest device telemetry used for safety checks. */\n device_capability?: {\n agent_version?: string | null;\n gpu_delegate_supported?: boolean | null;\n gpu_name?: string | null;\n model_tier?: string | null;\n recent_failure_count?: number;\n soc_name?: string | null;\n soc_vendor?: string | null;\n telemetry_fresh?: boolean;\n };\n effective_mode?: SensingPerformanceMode;\n /** Despite the name this is not an apply OUTCOME and carries no device acknowledgement: it is `null` when the screen has no active profile and otherwise always `{ status: \"active_profile_loaded\", profile_id, updated_at }` describing the profile row that is loaded now. */\n last_apply_status?: Record<string, unknown> | null;\n /** The full selectable set with human labels (`auto` = \"Auto recommended\", `presence_only` = \"Viewer count only\", and so on). It is a static list, NOT filtered by this screen's hardware — a mode listed here can still be refused; read `safety.blocks` before offering it. */\n modes?: Array<{\n label?: string;\n value?: SensingPerformanceMode;\n }>;\n /** Hardware pre-flight for `effective_mode`. `allowed` is simply `blocks.length === 0`; `warnings` never block (stale telemetry, recent device failures), while a `block` — missing fresh telemetry for a load-increasing mode, or FLOOR-tier/CPU-only hardware for `advanced` — makes a PUT of that mode answer 409 with this object under `error.details.safety`. */\n safety?: {\n allowed?: boolean;\n blocks?: Array<{\n code?: string;\n message?: string;\n }>;\n warnings?: Array<{\n code?: string;\n message?: string;\n }>;\n };\n /** The screen's Mongo id — the same identifier the route surfaces call `screenMongoId`. Echoes the path parameter. */\n screen_id?: string;\n};\n\n/**\n * Webhook request BODY delivered when a Sense Anything Route fires. Unlike the\n * device/programmatic events, a Route fire delivers a FLAT, self-describing\n * payload (no `{event,timestamp,data}` wrapper) — the event type is in the\n * `X-Trillboards-Event` request header (`sensing.threshold_crossed` for a custom\n * sensed field, or the matching `audience.*` / `venue.*` event for a known\n * semantic; a scheduled-digest Route delivers `venue.traffic_summary`). All Route\n * events share this same body shape. Verify the `X-Trillboards-Signature` header\n * over the raw body before trusting it.\n *\n */\nexport type SensingThresholdCrossedEvent = {\n /** The sensed field key evaluated (null for count-only digests). */\n field?: string | null;\n /** Server clock when the evaluator fired — not when the camera saw it. On the cloud path it was measured 8.2 s median and 15.3 s p95 behind the frame's shutter, so use the payload's `captured_at` (the device's own shutter instant) whenever the fire carried a frame. */\n fired_at?: string;\n /** The observed value that crossed the threshold. */\n observed?: unknown;\n /** The comparison the evaluator applied. `null` for a scheduled digest, which aggregates rather than comparing. Numeric operators only ever match when both the observed value and the threshold parse as finite numbers. */\n op?: \"gt\" | \"gte\" | \"lt\" | \"lte\" | \"eq\" | \"neq\" | null;\n /** Who owns the route that fired. Webhooks are dispatched to the SCREEN's partner regardless of who created the route, so a partner endpoint legitimately receives `earner_user` fires for routes the screen's own operator configured and which are not visible through the Partner API. */\n owner_kind?: \"earner_user\" | \"partner\";\n /** The Route that fired. */\n route_id?: string;\n /** The route's operator-supplied label at the time of the fire. It is mutable and not unique — match on `route_id` for idempotency and routing. */\n route_name?: string;\n /** The screen (mongo id) whose observation crossed the threshold. */\n screen_id?: string;\n /** The configured threshold value (number, string, or boolean). May be null. */\n threshold?: unknown;\n};\n\nexport type SsdpDeviceInfo = {\n /** The `<friendlyName>` element from the fetched description XML. If it looks like a personal name (possessive apostrophe-s, an email, a MAC) the ENTIRE row is discarded rather than the field being blanked, so consumer devices named after their owner never appear at all. */\n friendlyName?: string | null;\n /** The `LOCATION:` header from the M-SEARCH response — the URL of the device description XML, and the de-duplication key for the whole list (one row per distinct LOCATION, last response wins). A response without it produces no row at all. */\n location: string;\n /** The `<manufacturer>` element from the description XML. `null` whenever the description fetch was skipped, timed out or failed — which is common, since fetching is bounded to half the listen window with a 1.5 s per-host timeout — so absence says nothing about the device. */\n manufacturer?: string | null;\n /** The `<modelName>` element from the description XML, field-stripped (not row-dropped) if it contains an email or a MAC. Absent on every row whose description fetch did not complete. */\n modelName?: string | null;\n /** The raw `SERVER:` response header, an RFC 2616 product token such as `Linux UPnP/1.0 Sonos/72.6-31040`. It comes from the M-SEARCH response itself, so unlike the XML-derived fields below it is present even when the description fetch fails — and it is deliberately neither PII-filtered nor hashed server-side, being treated as a low-cardinality vendor hint. */\n server?: string | null;\n /** The `ST:` search-target header of the response, e.g. `urn:schemas-upnp-org:device:ZonePlayer:1`. The probe searches with `ST: ssdp:all`, so this reflects what the device chose to answer as, not what we asked for. It arrives with the M-SEARCH response, so it survives a failed description fetch; an empty string means the responder omitted the header. */\n st: string;\n /** The `<UDN>` element — the device's self-declared Unique Device Name, typically a `uuid:` URN and the most stable per-device key SSDP offers. It is hashed server-side, and it is absent whenever the description fetch did not complete. */\n udn?: string | null;\n [key: string]: unknown;\n};\n\nexport type TeamInviteResponse = {\n /** The invited address, lower-cased and trimmed. Once the invitation is accepted the listing reports the accepting user account's email instead, which may differ from this one. */\n email?: string;\n /** When the invitation expires (7 days from creation) */\n expires_at?: string;\n /** A freshly minted 24-character hex ObjectId held as the membership's legacy id — NOT the `memberships` UUID primary key. It is the value the team routes take as the member path parameter. */\n membership_id?: string;\n /** Only `admin` and `viewer` can be invited — `owner` is the membership created with the partner account and cannot be granted through this endpoint. */\n role?: \"admin\" | \"viewer\";\n /** `active` when the invited email already had a Trillboards account: the membership is auto-accepted at invite time, `accepted_at` is set immediately, no token is issued and `expires_at` is `null`. `pending` otherwise, with a 7-day invite token. Either way the team is capped at 50 active-or-pending members. */\n status?: \"pending\" | \"active\";\n};\n\nexport type TeamMember = {\n /** `null` while `status` is `pending`. Set at INVITE time rather than click time when the invitee already had a Trillboards account and the membership was auto-activated. */\n accepted_at?: string | null;\n /** The linked user account's email once the invitation is accepted; the address the invitation was SENT to while `status` is `pending`. The two differ if the invitee accepts with a different account. */\n email?: string;\n /** When the invitation row was created. Re-inviting the same address rotates the token and moves this timestamp forward. */\n invited_at?: string;\n /** Name and email of the inviting user, resolved by join. `null` for the owner membership created with the partner account, and for any invitation whose inviter row no longer resolves. */\n invited_by?: {\n email?: string;\n name?: string;\n } | null;\n /** **Always `null`.** The column has no counterpart in `memberships` after the tenant-membership cutover, so the canonical reader projects a literal `NULL::timestamptz` and nothing writes it. */\n last_portal_access?: string | null;\n /** Unique membership record ID */\n membership_id?: string;\n /** The linked user account's display name. Undefined — and so omitted from the JSON — while `status` is `pending`, because no user row is attached until the invitation is accepted. */\n name?: string;\n permissions?: RolePermissions;\n /** Profile photo URL from the linked user account, and null/absent while the invitation is pending for the same reason as `name`. */\n photo?: string | null;\n /** `owner` is the membership created with the partner account and can never be invited or granted — only `admin` and `viewer` are grantable. Role alone determines `permissions`. */\n role?: \"owner\" | \"admin\" | \"viewer\";\n /** The listing returns `active` + `pending` only; `revoked` rows appear only when `?status=revoked` is passed explicitly. An invitation to an email that already has a Trillboards account is auto-accepted and lands `active` immediately. */\n status?: \"active\" | \"pending\" | \"revoked\";\n /** User ID (null if pending invitation) */\n user_id?: string;\n};\n\nexport type Transaction = {\n /** Integer US cents. Earnings rows are allocated cumulatively so the list sums EXACTLY to the lifetime total — an individual row can therefore differ by one cent from rounding that day's dollars on its own. */\n amount_cents?: number;\n /** For a `payout`, the real creation timestamp. For an `earning` it is `new Date(period_date)` — midnight UTC of the accounting day, a value derived from the date key rather than the time anything happened. */\n created_at?: string;\n /** For an `earning` row, the `YYYY-MM-DD` accounting period the aggregate covers. For a `payout` row it is the payout record's creation timestamp, so it carries a time component despite the declared `date` format. */\n date?: string;\n earning_source_version?: 1;\n earning_sources?: EarningsSources;\n /** Synthetic and namespaced by origin, never a ledger primary key: earnings rows are `daily_<YYYY-MM-DD>` (one per accounting day, not per impression); payouts are `admin_batch:<ledger_id>`, `earner_withdrawal:<withdrawal_id>` or `legacy_transfer:<id>`. */\n id?: string;\n /** Number of impressions (for earning transactions) */\n impressions?: number;\n /** Source of the transaction (e.g., impressions, payout) */\n source?: string;\n /** Stripe transfer ID (for payout transactions) */\n stripe_transfer_id?: string;\n /** `earning` rows are daily revenue aggregates from `programmatic_earnings_daily`; `payout` rows are money leaving, unioned from three sources and deduplicated by provider transfer id. `?type=all` (the default) merges both and sorts newest-first. */\n type?: \"earning\" | \"payout\";\n};\n\n/**\n * The canonical Trillboards error envelope. Emitted by `middleware/globalErrorHandler.js` for every error that reaches it: `utils/apiErrors.ts` `toJSON()` builds the inner object and the handler wraps it in `{ error }` and stamps `request_id`.\n */\nexport type TrillboardsError = {\n error: {\n /** Stable machine-readable error code. Switch on this, never on `message`. The 143 CATALOGUED codes are published as the `ErrorCode` schema and served live by `GET /v1/errors`, which is the authority. This field is NOT limited to them: services emit further codes directly, and `ApiError.notFound(resource)` mints `<resource>_not_found` from an arbitrary resource name. Always handle an unrecognised code by falling back to `type`. */\n code: string;\n /** Per-field violations (populated from Zod errors). */\n details?: Array<{\n code?: string;\n message?: string;\n param?: string;\n }>;\n /** Documentation for this code — https://api.trillboards.com/docs/errors#<code>. */\n doc_url?: string;\n /** Agent-facing hints and suggestions. */\n help?: {\n portal_url?: string;\n suggestions?: Array<string>;\n summary?: string;\n try_next?: string;\n };\n /** Human-readable error message. Safe to surface to end-users. */\n message: string;\n /** Offending parameter name, or null when the error is not field-scoped. */\n param?: string | null;\n /** Echoed X-Request-Id. Quote this to support for log correlation. */\n request_id: string;\n type: ErrorType;\n };\n};\n\n/**\n * Partial, non-clobbering update — include only the fields to change. Update reviewTimingMode and reviewWaitSeconds together; omit both to preserve existing timing. Explicit timing cannot be added to a digest, and changing to a digest clears the prior event check timing.\n */\nexport type UpdatePartnerSensingRouteRequest = {\n /** REPLACES the entire action list (1..10 entries); the existing rows are deleted and reinserted. Omit the key to leave actions untouched — there is no way to add or remove a single action. */\n actions?: Array<SensingRouteAction>;\n /** Seven-bit weekday mask evaluated in `activeTz`: bit 0 is Monday through bit 6 is Sunday, so 127 is every day and 0 never fires. Requires `activeTz`, like the rest of the active window. */\n activeDaysMask?: number;\n /** Inclusive end of the daily active window, minutes past local midnight in `activeTz` (0..1439). A value below `activeStartMinute` is read as an overnight window, not an empty one. */\n activeEndMinute?: number;\n /** Inclusive start of the daily active window, minutes past local midnight in `activeTz` (0..1439). Requires `activeTz` to have any effect. */\n activeStartMinute?: number;\n /** IANA zone name the active window is evaluated in. Setting the minute/day fields without this leaves the window inert, and an unparseable zone fails open (always active) rather than suppressing alerts. */\n activeTz?: string;\n condition?: SensingRouteCondition;\n /** Minimum gap in seconds between alerts for the same (route, screen) episode, clamped to 0..86400. `0` means fire on every rising edge with no spacing at all. */\n debounceSeconds?: number;\n /** Replaces the digest block and, as a side effect, rewrites `condition` to `{ field }` or `null`. `field` is required for `avg` and `max`; `intervalMinutes` must be at least 5 because the digest cron only runs every 5 minutes. */\n digest?: {\n field?: string;\n intervalMinutes?: number;\n metric?: \"count\" | \"avg\" | \"max\";\n windowMinutes?: number;\n };\n /** Only the JSON literal `true` enables the route here — every other value, including the string `\"true\"` and `1`, is written as `false`. This is the mirror image of create, where only the literal `false` disables, so send a real boolean. */\n enabled?: boolean;\n /** Replaces the label. Must be a non-empty string after trimming and at most 200 characters; sending an empty or whitespace-only name is a 400 rather than a clear. */\n name?: string;\n /** Rebinds the route to a different named sensing profile; send `null` (or an empty/whitespace string) to unbind it. Always a profile NAME — a `profile_id` is re-minted on every redeploy and would orphan the binding. */\n profileName?: string | null;\n reviewTimingMode?: SensingRouteReviewTimingMode;\n reviewWaitSeconds?: SensingRouteReviewWaitSeconds;\n /** Replace all selected rectangles or polygon lassos for the route; send [] to return every camera to whole view. Entries must target the route's explicit screenMongoIds. */\n screenFocusRegions?: Array<SensingRouteScreenFocusRegion>;\n /** REPLACES the watched screen set (1..250, deduped); mutually exclusive with `targetGroupId` in one request. Focus regions for cameras that survive the swap are PRESERVED unless you also send `screenFocusRegions` (including `[]` to clear them). */\n screenMongoIds?: Array<string>;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainMinSamples?: number;\n /** How long the condition must hold before the FIRST alert, in seconds (600 = ten minutes). Omit to alert on the first true reading. Cadence-independent, because the evaluator compares timestamps - correct however often the screen reports, including when that rate changes while the condition holds. */\n sustainSeconds?: number;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainWindowSeconds?: number;\n /** Set to a sg_ group id to (re)target the group's live members, or null to switch back to an explicit screenMongoIds set. Mutually exclusive with screenMongoIds in one request. */\n targetGroupId?: string | null;\n};\n\n/**\n * The resolved programmatic VAST waterfall. `sources` is an ORDERED ARRAY and clients MUST iterate ALL of it — by `priority` ascending, skipping `enabled: false` — advancing to the next source on no-fill, empty VAST, timeout, IMA load error AND playback error (VAST 303 / 300 / 301 / 1009 all mean \"try the next source\", never \"stop\"). Reading `sources[0]` and stopping is the most common integration defect and reads as \"no demand\" when demand exists further down. Apply each source's own `timeout_ms`, and cap the whole loop at `total_timeout_ms`. Always handle player error and never leave the screen black; always fire the VAST `<Error>` beacon with `[ERRORCODE]` substituted. Full contract: docs/integrations/partner-native-ima-sdk.md §0 and §4.1. SQL/Admin-enabled sources remain visible; request-pressure decisions are enforced, when enabled, at first-party upstream fetch seams rather than by removing sources from this catalog.\n *\n */\nexport type VastWaterfall = {\n /** Derived, not configured: it is simply `sources.length > 0`. `false` means no demand source resolved for this screen on this request, so fall back to your own content. It is not a partner-settable toggle and it does not mean programmatic serving is switched off for the screen. */\n enabled?: boolean;\n /** Every source must be attempted, by `priority` ascending, until one actually plays. Not a preference list. */\n sources?: Array<VastWaterfallSource>;\n /** Budget in ms for the ENTIRE waterfall loop, across all sources. When it is spent, stop trying sources and return to your own content. */\n total_timeout_ms?: number;\n} | null;\n\n/**\n * One demand source in the VAST waterfall. Fetch `vast_url` (already a Trillboards vast-wrap URL — use as-is) and pass the response to your IMA SDK. A failure here means \"advance to the next source\", not \"the request had no demand\".\n */\nexport type VastWaterfallSource = {\n /** Identical across every source in this response — one id per waterfall build, so it groups all of a single ad opportunity's source attempts, while `request_id` identifies this source alone. Echo it back; never mint your own. */\n auction_request_id?: string;\n /** The source's effective FLOOR CPM in dollars per thousand impressions (never cents) — the configured tag floor (`NUMERIC(8,2)`) or the partner's default, after any demand price action. It is an expectation used to order the waterfall, not a cleared price and not what the screen earns; `null` when the partner has no floor configured. */\n cpm?: number | null;\n /** High recent error rate measured server-side. Degraded sources are already pushed to the tail of the array for you — do NOT skip a source because of this flag. */\n degraded?: boolean;\n /** Whole seconds of the creative THIS source will serve, so you can schedule continuous loop playback without first fetching the VAST and probing the MP4. Present only on the first-party `trillboards_direct` source (a programmatic source's length is not known until its own VAST is fetched). It is the same number as the `<Duration>` in the VAST that `vast_url` returns, so use either — they cannot disagree. NOT a playback control: IMA still takes its real timing from the media, so treat this as scheduling metadata and keep honouring the actual quartile/complete events. A creative longer than your ad slot will still be cut off by the next poll; we warn the uploader at upload time when that is the case. */\n duration_seconds?: number;\n /** When false, skip this source entirely. Present-but-disabled sources may appear in the array. */\n enabled?: boolean;\n /** Recent non-response rate for this demand source as a PERCENT 0-100 (not a 0-1 fraction). It is fleet-wide rather than per-screen, derived from an hourly rollup that can be ~15 minutes stale, and it is present only on sources that also carry `degraded: true`. Advisory: it never means skip the source. */\n error_rate_pct?: number;\n /** No live producer — `null` on every source. The only assignment is guarded on a partner-level `fetchMode` setting that no demand partner in the registry defines. Do not branch on it. */\n fetch_mode?: string | null;\n /** Opaque public alias for the demand source. Echo it back as `variant_name` on `programmatic-event` reports. */\n name?: string;\n /** Opaque ascending sort key — lowest is tried first. Do NOT treat it as an index: it may start at 0 (a source we want to win outright — the `?test=1` source, an injected first-party bid, an auction winner — is stamped `priority: 0`), and values may be non-contiguous (some paths renumber survivors 1..N, others keep catalog values). Only the relative order is meaningful; sort ascending and do no arithmetic on it. */\n priority?: number;\n /** Per-source request identifier. Report against the source you actually used, not `sources[0]`. */\n request_id?: string;\n /** Per-source budget in ms for fetching and loading THIS source. Distinct from the waterfall-wide `total_timeout_ms`. */\n timeout_ms?: number;\n /** Fetch verbatim. The server has already appended this source's `request_id` and rewritten every partner-identifying path segment and query parameter to an opaque alias, so editing, re-ordering or re-signing the query breaks impression attribution. A `data:` URL can appear here for an inbound-DSP source that carries its VAST inline. */\n vast_url?: string;\n};\n\nexport type Webhook = {\n /** When webhook was created */\n created_at?: string;\n /** Subscribed event types */\n events?: Array<\"device.online\" | \"device.offline\" | \"impression.recorded\" | \"creative.rewards_ready\" | \"campaign.allocated\" | \"payout.processed\" | \"sensing.threshold_crossed\" | \"audience.spike\" | \"audience.venue_busy\" | \"audience.purchase_intent\" | \"audience.demographics_update\" | \"venue.traffic_summary\" | \"screen_group.created\" | \"screen_group.updated\" | \"screen_group.deleted\" | \"screen_group.member.added\" | \"screen_group.member.removed\" | \"screen_group.content_policy.updated\" | \"screen_group.sensing_config.updated\" | \"fleet_command.completed\" | \"fleet_command.failed\">;\n /** Total failed deliveries */\n failure_count?: number;\n /** Last time webhook was triggered */\n last_triggered_at?: string;\n /** Webhook signing secret (whsec_…). Returned when the webhook is created so partners can verify the compound `X-Trillboards-Signature` header over the raw payload (see the SDK's Webhooks.constructEvent). Treat as a credential — store it securely and never expose it client-side. */\n secret?: string;\n /** Webhook status */\n status?: \"active\" | \"inactive\";\n /** Total successful deliveries */\n success_count?: number;\n /** Webhook endpoint URL */\n url?: string;\n /** Unique webhook identifier */\n webhook_id?: string;\n};\n\nexport type WebhookDelivery = {\n /** When delivery was first attempted */\n attempted_at?: string;\n /** When delivery succeeded */\n delivered_at?: string;\n /** Error message if failed */\n error_message?: string;\n /** Event that triggered this delivery */\n event_type?: string;\n /** HTTP status code from endpoint */\n http_status?: number;\n /** Delivery record ID */\n id?: string;\n /** Response time in milliseconds */\n response_time_ms?: number;\n /** Number of retry attempts */\n retry_count?: number;\n /** Delivery status */\n status?: \"pending\" | \"success\" | \"failed\" | \"retrying\";\n};\n\n/**\n * Standard webhook event payload sent to your endpoint.\n * All events follow this structure.\n *\n */\nexport type WebhookEvent = {\n /** Event-specific data */\n data?: Record<string, unknown>;\n /** Event type */\n event?: string;\n /** When event occurred */\n timestamp?: string;\n};\n\nexport type WifiEnvironmentSnapshot = {\n /** Dimensionless 0.0-1.0: visible APs whose centre frequency is within ±10 MHz of the joined AP, divided by ALL visible APs in the capped list. Because the joined AP is itself in the numerator it is never 0 while connected — a literal `0.0` means not connected (or an empty scan), not an empty channel. Above ~0.5 indicates a dense venue, below ~0.2 an isolated one. */\n channelCongestionRatio?: number | null;\n /** Channel width of the joined AP in MHz — but it is APPROXIMATE by construction: the producer returns the width of the FIRST entry in the scan list, which is only usually the joined AP, rather than matching on BSSID. `null` when not connected, on Android below API 23, or for an 80+80 MHz AP, which the width mapping does not cover. */\n connectedChannelWidthMhz?: number | null;\n /** Centre frequency of the joined AP's primary 20 MHz channel, in MHz. `0` means not connected / unavailable, and that zero also zeroes `channelCongestionRatio` and forces `frequencyBand` to `unknown`. */\n connectedFrequencyMhz?: number | null;\n /** Negotiated PHY link speed of the joined AP in Mbps (`WifiInfo.getLinkSpeed()`) — a link-rate ceiling, not measured throughput. `null` rather than 0 when not connected or when the platform reports a non-positive value. */\n connectedLinkSpeedMbps?: number | null;\n /** RSSI of the JOINED access point in dBm. `-100` is a SENTINEL, not a measurement: it is the literal default when `WifiInfo` is unavailable or the device is not on Wi-Fi, so a genuinely weak link and no link at all are indistinguishable here. */\n connectedSignalDbm?: number | null;\n /** Band of the JOINED AP: `2.4ghz`, `5ghz`, `6ghz` or `unknown`. It is derived from `connectedFrequencyMhz`, so a device that is not on Wi-Fi reports `unknown` rather than null — and the 5900-5924 MHz range falls into `unknown` too, because the classifier's 5 GHz and 6 GHz windows do not meet. */\n frequencyBand?: string | null;\n /** Median RSSI in dBm across ALL visible APs (not just the joined one). It is the UPPER median for an even-sized list — the two middle values are not averaged — and falls back to the `-100` sentinel on an empty list. */\n medianSignalDbm?: number | null;\n /** Distinct BSSIDs in the scan, after the 50-entry cap. It is literally the SAME value as `uniqueBssidCount` — both are assigned from one field — so the two never disagree and neither is a count of the uncapped scan. */\n networkCount?: number | null;\n /** Population VARIANCE of the joined AP's RSSI in dBm SQUARED — not a standard deviation, and the divisor is N, not N-1. The window is the last 5 HEARTBEAT cycles held in a process-lifetime buffer, not the current scan, so it reads `0.0` on the first cycle after an agent restart; the `-100` not-connected sentinel is admitted into that history and skews it. Intended as a coarse motion indicator, not occupancy. */\n rssiVariance?: number | null;\n /** UNIX EPOCH milliseconds (`System.currentTimeMillis()`) stamped when the scan COMPLETED — wall clock, not boot-relative, and not the scan start. It is a different clock from Android's own per-result `ScanResult.timestamp`, which is microseconds since boot and never reaches the wire. */\n scanTimestampMs?: number | null;\n /** Strongest minus weakest visible RSSI, in dB — a positive difference, not an absolute level. It is `0` when fewer than two APs are visible, which is indistinguishable from a genuinely uniform RF environment. Used as a venue-openness hint. */\n signalSpreadDbm?: number | null;\n /** Distinct BSSIDs after de-duplication and the 50-entry cap — the identical value to `networkCount` above, assigned from the same field, so it adds no independent information. */\n uniqueBssidCount?: number | null;\n [key: string]: unknown;\n} | null;\n\nexport type WifiScanResult = {\n /** Bonded channel width in MHz, mapped from Android's width enum to a literal 20, 40, 80 or 160. `null` on Android below API 23, on any read failure, AND for an 80+80 MHz AP — that variant has no mapping, so `null` does not mean narrow. */\n channelWidthMhz?: number | null;\n /** Centre frequency of the AP's PRIMARY 20 MHz channel in MHz (2400-7200 accepted, covering 2.4/5/6 GHz) — not the centre of a wider bonded channel. Convert to a channel number yourself; the wire carries only the frequency. */\n frequencyMhz: number;\n /** The AP's BSSID exactly as Android reports it, unhashed on the device; Trillboards stores it as received and as a keyed HMAC-SHA256 hash. Rows are de-duplicated by BSSID keeping the strongest reading, and the literal `02:00:00:00:00:00` — Android's redaction placeholder for a caller without location permission — is dropped rather than forwarded, so a permission-less device produces an empty list, not a list of placeholders. */\n rawBssid: string;\n /** AP signal level in dBm (negative, bounded -120..0), from `ScanResult.level`. Note the wire key the Android agent actually sends is `signal_dbm`, not a snake_case form of this name — a mismatch of exactly this kind left the ClickHouse `rssi` column NULL on 100% of Wi-Fi rows until 2026-08-08. */\n signalStrengthDbm: number;\n [key: string]: unknown;\n};\n\n\n// ─── Operations ──────────────────────────────────────────────────────────\n\n/**\n * Query API changelog\n * GET /v1/changelog\n */\nexport interface queryChangelogOperation {\n readonly path: \"/v1/changelog\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** Only entries dated on or after this date (YYYY-MM-DD). Unreleased entries always included. */\n since?: string;\n /** Filter by change category: \"breaking\" (changed+removed), \"additive\" (added), \"deprecation\" (deprecated), \"fix\" (fixed). Comma-separated for multiple. */\n type?: string;\n};\n response: ChangelogResponse;\n}\n\n/**\n * Ad-sighting records: outdoor ads our cab cameras read, each with a signed photo link.\n * GET /v1/data/ad-sightings\n */\nexport interface dataApiAdSightingsOperation {\n readonly path: \"/v1/data/ad-sightings\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** A board the sighting was matched to, repeatable. */\n board_id?: Array<string> | null;\n /** A brand, repeatable for a brand set (competitors). Matched however it is typed: \"McDonald's\", \"mcdonalds\" and \"Mc Donalds\" find the same records. */\n brand?: Array<string> | null;\n /** A resolved brand id, repeatable. Combined with `brand` as either-or. */\n brand_id?: Array<string> | null;\n /** A sighted creative (the campaign artwork), repeatable. */\n creative_id?: Array<string> | null;\n /** The `next_cursor` of the previous page. */\n cursor?: string;\n /** Start (inclusive), YYYY-MM-DD or an ISO-8601 instant. Default: 30 days before `to`. */\n from?: string;\n /** The reading instrument (ssr-v1, vlmtext-v1, edge_ocr_v1), repeatable. */\n instrument?: Array<string> | null;\n /** Records per page, 1..500 (default 50). The page limit is reserved; the records returned are billed. */\n limit?: number;\n /** A market as COUNTRY:CODE (US DMA or CA CMA), repeatable. */\n market?: Array<string> | null;\n /** The surface kind as read (billboard, storefront, bus_shelter, ...), repeatable. */\n surface?: Array<string> | null;\n /** End (exclusive). Default: now. The span is at most 400 days. */\n to?: string;\n};\n response: DataApi_AdSightingsResponse;\n}\n\n/**\n * Outdoor creative history by brand, market and month, from the creative ledger.\n * GET /v1/data/ad-sightings/history\n */\nexport interface dataApiAdSightingsHistoryOperation {\n readonly path: \"/v1/data/ad-sightings/history\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** A brand by name, repeatable (competitors). Resolved to its brand id the way the brand search does. */\n brand?: Array<string> | null;\n /** A resolved brand id, repeatable. */\n brand_id?: Array<string> | null;\n /** The `next_cursor` of the previous page. */\n cursor?: string;\n /** First month (YYYY-MM) or day. Default: 12 months before `to`. */\n from?: string;\n /** market_month (default) or fleet_month. */\n grain?: \"market_month\" | \"fleet_month\";\n /** Rows per page, 1..500 (default 100). The page limit is reserved; the rows returned are billed. */\n limit?: number;\n /** A market as COUNTRY:CODE, repeatable. */\n market?: Array<string> | null;\n /** Last month (YYYY-MM, inclusive) or day. Default: this month. */\n to?: string;\n};\n response: DataApi_AdSightingsHistoryResponse;\n}\n\n/**\n * Aggregate audience analytics for a time window.\n * GET /v1/data/aggregate\n */\nexport interface dataApiAggregateOperation {\n readonly path: \"/v1/data/aggregate\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** Dimension to group aggregations by. */\n group_by?: \"venue_type\" | \"daypart\" | \"day_of_week\" | \"income_estimated_level\" | \"primary_mood\";\n /** Metric to aggregate on. */\n metric?: \"avg_face_count\" | \"avg_attention\" | \"income_confidence\" | \"max_face_count\";\n /** Relative time window (e.g. \"24h\", \"7d\"). */\n since?: string;\n};\n response: DataApi_AggregateResponse;\n}\n\n/**\n * Find screens with similar audience profiles.\n * GET /v1/data/lookalike\n */\nexport interface dataApiLookalikeScreensOperation {\n readonly path: \"/v1/data/lookalike\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n min_similarity?: number;\n /** Internal screen identifier. */\n screen_id: string;\n};\n response: DataApi_LookalikeResponse;\n}\n\n/**\n * Composable query DSL over approved datasets.\n * POST /v1/data/query\n */\nexport interface dataApiQueryDslOperation {\n readonly path: \"/v1/data/query\";\n readonly method: \"POST\";\n request: DataApi_QueryDslBody;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: DataApi_QueryDslResponse;\n}\n\n/**\n * List screens visible to the caller.\n * GET /v1/data/screens\n */\nexport interface dataApiListScreensOperation {\n readonly path: \"/v1/data/screens\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** ISO-3166 alpha-2 country code. */\n country?: string;\n limit?: number;\n offset?: number;\n /** Filter by venue taxonomy type (e.g. bar, restaurant). */\n venue_type?: string;\n};\n response: DataApi_ScreensListResponse;\n}\n\n/**\n * Real-time audience snapshot for a screen.\n * GET /v1/data/screens/{screenId}/audience\n */\nexport interface dataApiScreenAudienceLiveOperation {\n readonly path: \"/v1/data/screens/{screenId}/audience\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** Internal screen identifier. */\n screenId: string;\n};\n query: Record<string, unknown>;\n response: DataApi_ScreenAudienceResponse;\n}\n\n/**\n * Historical audience_metrics rows for a screen.\n * GET /v1/data/screens/{screenId}/history\n */\nexport interface dataApiScreenAudienceHistoryOperation {\n readonly path: \"/v1/data/screens/{screenId}/history\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** Internal screen identifier. */\n screenId: string;\n};\n query: {\n limit?: number;\n /** ISO-8601 start timestamp. */\n since?: string;\n /** ISO-8601 end timestamp. */\n until?: string;\n};\n response: DataApi_ScreenHistoryResponse;\n}\n\n/**\n * Similarity search over scene embeddings.\n * GET /v1/data/semantic-search\n */\nexport interface dataApiSemanticSearchOperation {\n readonly path: \"/v1/data/semantic-search\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n min_similarity?: number;\n q: string;\n /** Relative time window (e.g. \"24h\", \"7d\"). */\n since?: string;\n};\n response: DataApi_SemanticSearchResponse;\n}\n\n/**\n * Platform-wide counters.\n * GET /v1/data/stats\n */\nexport interface dataApiStatsOperation {\n readonly path: \"/v1/data/stats\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: DataApi_StatsResponse;\n}\n\n/**\n * Server-Sent Events stream of live audience/observation events.\n * GET /v1/data/stream\n */\nexport interface dataApiStreamOperation {\n readonly path: \"/v1/data/stream\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n observation_family?: string;\n venue_type?: string;\n};\n response: unknown;\n}\n\n/**\n * API usage for the authenticated key (billing dashboard).\n * GET /v1/data/usage\n */\nexport interface dataApiUsageOperation {\n readonly path: \"/v1/data/usage\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n days?: number;\n};\n response: DataApi_UsageResponse;\n}\n\n/**\n * List all error codes\n * GET /v1/errors\n */\nexport interface listErrorCodesOperation {\n readonly path: \"/v1/errors\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: ErrorListResponse;\n}\n\n/**\n * Look up a single error code\n * GET /v1/errors/{code}\n */\nexport interface getErrorCodeOperation {\n readonly path: \"/v1/errors/{code}\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** A stable error code from the catalog. See GET /v1/errors for the full list. */\n code: string;\n};\n query: Record<string, unknown>;\n response: ErrorCodeResponse;\n}\n\n/**\n * Example healthcheck (Phase 3 pipeline proof)\n * GET /v1/example/healthcheck\n */\nexport interface exampleHealthcheckOperation {\n readonly path: \"/v1/example/healthcheck\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** When `true`, include diagnostic fields in the response. */\n verbose?: \"true\" | \"false\";\n};\n response: DataApi_ExampleHealthcheckResponse;\n}\n\n/**\n * List self-promo ads\n * GET /v1/partner/ads/self-promo\n */\nexport interface listPartnerSelfPromoAdsOperation {\n readonly path: \"/v1/partner/ads/self-promo\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: Array<PartnerSelfPromoAd>;\n success?: boolean;\n};\n}\n\n/**\n * Create self-promo ad (URL-based)\n * POST /v1/partner/ads/self-promo\n */\nexport interface createPartnerSelfPromoAdOperation {\n readonly path: \"/v1/partner/ads/self-promo\";\n readonly method: \"POST\";\n request: PartnerSelfPromoCreate;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: PartnerSelfPromoAd;\n success?: boolean;\n warnings?: PartnerCreativeWarnings;\n};\n}\n\n/**\n * List self-promo placements by screen\n * GET /v1/partner/ads/self-promo/placements\n */\nexport interface listPartnerSelfPromoPlacementsOperation {\n readonly path: \"/v1/partner/ads/self-promo/placements\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: Array<PartnerSelfPromoPlacement>;\n success?: boolean;\n};\n}\n\n/**\n * Schedule existing self-promo ad to screens\n * POST /v1/partner/ads/self-promo/schedule\n */\nexport interface schedulePartnerSelfPromoOperation {\n readonly path: \"/v1/partner/ads/self-promo/schedule\";\n readonly method: \"POST\";\n request: {\n ad_id: string;\n end_date?: string;\n screen_ids: Array<string>;\n start_date?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Create self-promo ad (upload)\n * POST /v1/partner/ads/self-promo/upload\n */\nexport interface createPartnerSelfPromoUploadOperation {\n readonly path: \"/v1/partner/ads/self-promo/upload\";\n readonly method: \"POST\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: PartnerSelfPromoAd;\n success?: boolean;\n warnings?: PartnerCreativeWarnings;\n};\n}\n\n/**\n * Disable a self-promo ad\n * POST /v1/partner/ads/self-promo/{ad_id}/disable\n */\nexport interface disableSelfPromoAdOperation {\n readonly path: \"/v1/partner/ads/self-promo/{ad_id}/disable\";\n readonly method: \"POST\";\n request: never;\n params: {\n ad_id: string;\n};\n query: Record<string, unknown>;\n response: {\n ad_id?: string;\n status?: \"disabled\";\n};\n}\n\n/**\n * Register an AI agent\n * POST /v1/partner/agent/register\n */\nexport interface registerAgentOperation {\n readonly path: \"/v1/partner/agent/register\";\n readonly method: \"POST\";\n request: {\n /** Type of agent being registered */\n agent_type: \"dsp_agent\" | \"ssp_agent\" | \"trading_desk\" | \"analytics_platform\" | \"developer\";\n /** Company name (optional) */\n company?: string;\n /** Brief description of the agent's purpose */\n description?: string;\n /** Contact email */\n email: string;\n /** Agent or company name */\n name: string;\n /** Mint an AdCP **sandbox account** (`account.sandbox` in `get_adcp_capabilities`). Media buys made with the returned key validate end to end — screen resolution, published CPM/floor, flight parsing, derived impression goal, every error path — and return a well-formed response marked `sandbox: true`, but persist **no advertisements and no placements**. Nothing can serve, deliver, bill, or pay a venue. Use this for conformance and no-spend testing; register without it for a live account. */\n sandbox?: boolean;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n /** The AdCP **account** this key is bound to. Distinct from the `sandbox` block below, which describes auto-provisioned test SCREENS (supply); this is the account-level buying contract (demand) declared as `account.sandbox` in `get_adcp_capabilities`. */\n account?: {\n /** Seller-assigned account id. Same value as `partner_id`. */\n account_id?: string;\n note?: string;\n /** True when this is a sandbox account: its media buys write no advertisements and no placements, and every media-buy tool response is marked `sandbox: true`. */\n sandbox?: boolean;\n };\n agent_type?: string;\n /** API key for authentication (display once — save it!) */\n api_key?: string;\n /** MCP tools available to this agent */\n available_tools?: Array<string>;\n /** Exact same-key handoff for buyer/OpenRTB control-plane capabilities. */\n control_plane_onboarding?: {\n authorization?: string;\n endpoint?: string;\n method?: string;\n purpose?: string;\n reuses_api_key?: boolean;\n };\n docs_url?: string;\n /** Free tier limits per product */\n free_tiers?: Record<string, unknown>;\n mcp_endpoint?: string;\n mcp_transport?: string;\n /** Guidance for getting started */\n next_steps?: Array<string>;\n object?: string;\n /** Unique partner ID */\n partner_id?: string;\n pricing_url?: string;\n request_id?: string;\n /** Auto-provisioned test SCREENS (supply side). Unrelated to `account.sandbox`. */\n sandbox?: {\n note?: string;\n screens?: Array<{\n device_id?: string;\n name?: string;\n venue_type?: string;\n }>;\n };\n tools_url?: string;\n};\n}\n\n/**\n * Get partner analytics\n * GET /v1/partner/analytics\n */\nexport interface getAnalyticsOperation {\n readonly path: \"/v1/partner/analytics\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n end_date?: string;\n start_date?: string;\n};\n response: {\n data?: {\n devices_by_status?: Array<Record<string, unknown>>;\n impressions_by_day?: Array<{\n /** Date (YYYY-MM-DD) */\n _id?: string;\n impressions?: number;\n }>;\n partner?: {\n active_devices?: number;\n pending_payout_cents?: number;\n total_devices?: number;\n total_impressions?: number;\n total_revenue_cents?: number;\n };\n period?: {\n end?: string;\n start?: string;\n };\n };\n success?: boolean;\n};\n}\n\n/**\n * Visitor-flow continuity graph across the partner's screens\n * GET /v1/partner/analytics/audience-continuity\n */\nexport interface getAudienceContinuityOperation {\n readonly path: \"/v1/partner/analytics/audience-continuity\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n windowMinutes?: number;\n};\n response: {\n data?: {\n confidence?: string;\n sampleSize?: number;\n topPaths?: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * Audience composition for the partner's screens\n * GET /v1/partner/analytics/audience-profile\n */\nexport interface getAudienceProfileOperation {\n readonly path: \"/v1/partner/analytics/audience-profile\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n sampleSize?: number;\n segments?: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * BLE device discovery summary\n * GET /v1/partner/analytics/ble-summary\n */\nexport interface getBleSummaryOperation {\n readonly path: \"/v1/partner/analytics/ble-summary\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n totalObservations?: number;\n uniqueDevices?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Content performance — ad lineage and measured audience reactions\n * GET /v1/partner/analytics/content-performance\n */\nexport interface getContentPerformanceOperation {\n readonly path: \"/v1/partner/analytics/content-performance\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n groupBy?: \"canonical\" | \"wrapper\";\n includeUnclassified?: boolean;\n limit?: number;\n offset?: number;\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n byScreen?: Record<string, unknown>;\n creativeResponse?: Array<{\n adId?: string;\n advertiserName?: string;\n ageRating?: string;\n attentiveSeconds?: number;\n audioTerms?: Array<string>;\n avgAttention?: number;\n brandName?: string;\n confidence?: number;\n firstSeen?: string;\n iabCategories?: Array<string>;\n lastSeen?: string;\n moderationStatus?: string;\n plays?: number;\n viewers?: number;\n }>;\n /** Whether another page of creativeResponse exists after this one. */\n has_more?: boolean;\n nextOffset?: number | null;\n topAdvertisers?: Array<Record<string, unknown>>;\n /** Exact number of cards the cursor walks: the feed statement's count over every filter, taken before the page slice. Every page holds exactly `limit` cards except the last, so a walk at any page size reaches the end in ceil(total / limit) pages, and `nextOffset`/`has_more` describe the same set. Null when the feed could not be read (`degraded_fields` then lists `creativeResponse`). Describes the programmatic cursor; the offset-0 all-content block is additive. */\n total?: number | null;\n };\n status?: boolean;\n};\n}\n\n/**\n * Free-text search across the partner's creative catalogue\n * GET /v1/partner/analytics/creative-catalog\n */\nexport interface searchCreativeCatalogOperation {\n readonly path: \"/v1/partner/analytics/creative-catalog\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n offset?: number;\n q?: string;\n};\n response: {\n data?: {\n creatives?: Array<{\n adId?: string;\n advertiserName?: string;\n assetUrl?: string | null;\n brandName?: string;\n iabCategories?: Array<string>;\n moderationStatus?: string;\n }>;\n /** Whether another page of results exists after this one. */\n has_more?: boolean;\n nextOffset?: number | null;\n /** Always null for catalogue search: the read over-fetches limit+1 for a free `has_more`, but an exact total would need a second COUNT round-trip the search path deliberately avoids. Page to completion via `has_more`. */\n total?: number | null;\n };\n status?: boolean;\n};\n}\n\n/**\n * Everything your screens measured while a creative played\n * GET /v1/partner/analytics/creative-observations\n */\nexport interface getCreativeObservationsOperation {\n readonly path: \"/v1/partner/analytics/creative-observations\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n adId?: string;\n adIds?: string;\n contentSha256?: string;\n creativeId?: string;\n exact?: boolean;\n families?: string;\n from?: string;\n identity?: string;\n playLimit?: number;\n screenId?: string;\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\" | \"historical\";\n to?: string;\n};\n response: {\n code?: number;\n data?: {\n /** One rollup per creative (identity_key), every family with its count. */\n creatives?: Array<Record<string, unknown>>;\n /** Families that could not be read this time (their values are null). Absent when every family answered. */\n degraded_fields?: Array<string>;\n /** Why each family in degraded_fields was not read, keyed by family: `read_failed` (with its `code`); `pg_budget` for verified attention read on its Postgres home that did not finish within its budget (`budget_ms`), with the plays it read (`plays_read_n` of `plays_total_n`, `page_plays_read_n` of `page_plays_total_n`; a part read is never returned as the whole); `over_play_limit` with `limit` and `plays_total_n`. Absent when every family answered. */\n degraded_reasons?: Record<string, {\n budget_ms?: number;\n code?: string;\n limit?: number;\n page_plays_read_n?: number;\n page_plays_total_n?: number;\n plays_read_n?: number;\n plays_total_n?: number;\n reason: \"read_failed\" | \"pg_budget\" | \"over_play_limit\";\n }>;\n families?: Array<string>;\n /** The keys read, whether the read was exact, and the files and canonicals they resolved to. */\n identity?: Record<string, unknown>;\n /** The newest plays, each with its creative and every family read for it. */\n plays?: Array<Record<string, unknown>>;\n /** Every play of the creatives in the window, programmatic and supplied. */\n plays_total?: number;\n screen_ids?: Array<string>;\n /** The supplied plays read (plays_n, evidence_plays_n); null when the family was not read. */\n supplied?: Record<string, unknown> | null;\n window?: {\n from?: string;\n to?: string;\n };\n };\n message?: string;\n status?: boolean;\n};\n}\n\n/**\n * Scene-level observations for a played creative\n * GET /v1/partner/analytics/creative-scenes\n */\nexport interface getCreativeScenesOperation {\n readonly path: \"/v1/partner/analytics/creative-scenes\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n adId?: string;\n cursor?: string;\n exact?: boolean;\n identity?: string;\n limit?: number;\n screenId?: string;\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n /** The adId read; absent on an identity read. */\n adId?: string;\n /** The identity keys read (an identity read only). */\n identity?: string;\n nextCursor?: string | null;\n scenes?: Array<Record<string, unknown>>;\n /** The screen read; absent on an identity read across all your screens. */\n screenId?: string;\n timeRange?: string;\n total?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Per-pair flow directionality + transition heatmap\n * GET /v1/partner/analytics/cross-screen-flow\n */\nexport interface getCrossScreenFlowOperation {\n readonly path: \"/v1/partner/analytics/cross-screen-flow\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n edges?: Array<Record<string, unknown>>;\n screens?: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * Unified portfolio dashboard (earnings, ads, audience, screens, insights)\n * GET /v1/partner/analytics/dashboard\n */\nexport interface getPartnerAnalyticsDashboardOperation {\n readonly path: \"/v1/partner/analytics/dashboard\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n endDate?: string;\n period?: \"7d\" | \"30d\" | \"90d\" | \"custom\";\n startDate?: string;\n};\n response: {\n data?: {\n ads?: Record<string, unknown>;\n audience?: Record<string, unknown>;\n earnings?: {\n change?: Record<string, unknown>;\n current?: Record<string, unknown>;\n previous?: Record<string, unknown>;\n sparkline?: Array<Record<string, unknown>>;\n };\n insights?: Array<Record<string, unknown>>;\n period?: {\n endDate?: string | null;\n label?: string;\n startDate?: string | null;\n };\n screens?: Record<string, unknown>;\n topScreens?: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * Device-type / OS / browser-class distribution among repeat visitors\n * GET /v1/partner/analytics/device-suite-signature\n */\nexport interface getDeviceSuiteSignatureOperation {\n readonly path: \"/v1/partner/analytics/device-suite-signature\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n deviceTypes?: Array<Record<string, unknown>>;\n totalDevices?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * CSV export of partner-scope analytics (summary / daily / per-screen)\n * GET /v1/partner/analytics/export\n */\nexport interface exportPartnerAnalyticsOperation {\n readonly path: \"/v1/partner/analytics/export\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n endDate?: string;\n period?: \"7d\" | \"30d\" | \"90d\" | \"custom\";\n startDate?: string;\n type?: \"summary\" | \"daily-breakdown\" | \"screen-breakdown\";\n};\n response: unknown;\n}\n\n/**\n * Per-ad daily breakdown for the partner fleet\n * GET /v1/partner/analytics/export/ads-by-day\n */\nexport interface exportPartnerAdsByDayOperation {\n readonly path: \"/v1/partner/analytics/export/ads-by-day\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n endDate: string;\n format?: \"csv\" | \"json\";\n startDate: string;\n};\n response: {\n data?: Array<{\n ad_name?: string;\n advertiser_name?: string;\n campaign_type?: string;\n completion_rate?: number;\n date?: string;\n gross_revenue_cents?: number;\n plays_completed?: number;\n publisher_revenue_cents?: number;\n publisher_share_rate?: number;\n screen_name?: string;\n }>;\n status?: boolean;\n};\n}\n\n/**\n * 24-hour distribution across the partner's fleet\n * GET /v1/partner/analytics/fleet-hourly\n */\nexport interface getFleetHourlyOperation {\n readonly path: \"/v1/partner/analytics/fleet-hourly\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n /** One entry per hour of the day. A partner with no screens gets the empty form `{ hours: [], timeRange }` instead of an array. */\n data?: Array<Record<string, unknown>> | {\n hours?: Array<Record<string, unknown>>;\n timeRange?: string;\n };\n status?: boolean;\n};\n}\n\n/**\n * 4-stage discovery → awareness → engagement → conversion funnel\n * GET /v1/partner/analytics/halo-funnel\n */\nexport interface getHaloFunnelOperation {\n readonly path: \"/v1/partner/analytics/halo-funnel\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n stages?: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * 24-hour returning-vs-first-timer audience clock\n * GET /v1/partner/analytics/loyalty-clock\n */\nexport interface getLoyaltyClockOperation {\n readonly path: \"/v1/partner/analytics/loyalty-clock\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n hours?: Array<{\n firstTimerShare?: number;\n hour?: number;\n returningShare?: number;\n sampleSize?: number;\n }>;\n sampleSize?: number;\n timeRange?: string;\n };\n status?: boolean;\n};\n}\n\n/**\n * Fleet-wide audience summary for the partner's screens\n * GET /v1/partner/analytics/network-summary\n */\nexport interface getNetworkSummaryOperation {\n readonly path: \"/v1/partner/analytics/network-summary\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n dataAvailable?: boolean;\n queriedAt?: string;\n timeRange?: string;\n totalScreens?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Daily/hourly fleet-wide audience timeseries\n * GET /v1/partner/analytics/network-trend\n */\nexport interface getNetworkTrendOperation {\n readonly path: \"/v1/partner/analytics/network-trend\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n interval?: \"1h\" | \"1d\";\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n /** One bucket per `interval`. A partner with no screens gets the empty form `{ series: [], interval, timeRange }` instead of an array. */\n data?: Array<Record<string, unknown>> | {\n interval?: string;\n series?: Array<Record<string, unknown>>;\n timeRange?: string;\n };\n status?: boolean;\n};\n}\n\n/**\n * Cross-screen proximity-graph summary\n * GET /v1/partner/analytics/proximity-summary\n */\nexport interface getProximitySummaryOperation {\n readonly path: \"/v1/partner/analytics/proximity-summary\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n continuityEdges?: Array<Record<string, unknown>>;\n crossScreenSignals?: number;\n gateDiagnostic?: Record<string, unknown>;\n repeatSignals?: number;\n sourceBreakdown?: Record<string, unknown>;\n topPaths?: Array<Record<string, unknown>>;\n uniqueSignals?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Per-creative scatter of avg-attention vs revenue-per-impression\n * GET /v1/partner/analytics/revenue-attention-correlation\n */\nexport interface getRevenueAttentionCorrelationOperation {\n readonly path: \"/v1/partner/analytics/revenue-attention-correlation\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n points?: Array<Record<string, unknown>>;\n trendLine?: Record<string, unknown> | null;\n };\n status?: boolean;\n};\n}\n\n/**\n * Paginated chronological feed of scene observations\n * GET /v1/partner/analytics/scene-feed\n */\nexport interface getSceneFeedOperation {\n readonly path: \"/v1/partner/analytics/scene-feed\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n offset?: number;\n};\n response: {\n data?: {\n /** Distinct calendar days with observations over the last 90 days. */\n activeDays?: number;\n /** Whether another page exists after this one. */\n has_more?: boolean;\n nextOffset?: number | null;\n scenes?: Array<Record<string, unknown>>;\n /** Distinct observations within the feed window — the exact set the feed pages over. */\n total?: number;\n /** Distinct (screen, scene-prefix) observations over the last 90 days (gamification stat; independent of the page window). */\n totalObservations?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Latest scene description per screen\n * GET /v1/partner/analytics/scene-intelligence\n */\nexport interface getSceneIntelligenceOperation {\n readonly path: \"/v1/partner/analytics/scene-intelligence\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Present on EVERY response, success included. A field that appeared only on failure would make its absence the signal, and an absent field is indistinguishable from an old client or a version skew. */\n sceneDiagnostic: {\n /** True when the scene read completed and `scenes` reflects the fleet. False when it did not run to completion — `scenes` is then empty for want of an answer, not because the fleet is quiet. */\n measured: boolean;\n /** Null when `measured` is true. Otherwise names the operational failure. */\n reason: \"timeout\" | \"error\" | \"dependency_disabled\" | \"unknown\" | null | null;\n };\n scenes: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * Rotate API key\n * POST /v1/partner/api-key/rotate\n */\nexport interface rotateApiKeyOperation {\n readonly path: \"/v1/partner/api-key/rotate\";\n readonly method: \"POST\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** New API key — shown only once! */\n api_key?: string;\n grace_period_hours?: number;\n message?: string;\n /** When the old key stops working */\n previous_key_expires_at?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get partner-wide audience analytics\n * GET /v1/partner/audience-analytics\n */\nexport interface getAudienceAnalyticsOperation {\n readonly path: \"/v1/partner/audience-analytics\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n period?: \"24h\" | \"7d\" | \"30d\";\n};\n response: AudienceAnalyticsResponse;\n}\n\n/**\n * Export audience data\n * GET /v1/partner/audience-export\n */\nexport interface exportAudienceDataOperation {\n readonly path: \"/v1/partner/audience-export\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n aggregation?: \"hourly\" | \"daily\" | \"weekly\";\n end_date?: string;\n format?: \"json\" | \"csv\";\n screen_ids: string;\n start_date?: string;\n};\n response: AudienceExportResponse;\n}\n\n/**\n * Get audience heatmap data\n * GET /v1/partner/audience/heatmap\n */\nexport interface getAudienceHeatmapOperation {\n readonly path: \"/v1/partner/audience/heatmap\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n days?: number;\n screen_ids?: string;\n};\n response: {\n cells?: number;\n heatmap?: Array<Record<string, unknown>>;\n lookback_days?: number;\n object?: string;\n request_id?: string;\n screens_included?: number;\n};\n}\n\n/**\n * Get live audience data for a screen\n * GET /v1/partner/audience/live/{screenId}\n */\nexport interface getLiveAudienceOperation {\n readonly path: \"/v1/partner/audience/live/{screenId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n /** Null when screen is offline */\n audience?: {\n ad_receptivity?: number | null;\n attention_score?: number;\n crowd_density?: number;\n dominant_ambience?: string | null;\n dwell_time_ms?: number | null;\n emotional_engagement?: number | null;\n face_count?: number;\n income_level?: string | null;\n lifestyle_segment?: string | null;\n mood?: string | null;\n noise_level?: number | null;\n occupancy?: string | null;\n /** The speech classifier's reading when the screen reported one (`BROWSING`, `RESEARCHING`, `COMPARING`, `READY_TO_BUY`, `POST_PURCHASE`, `COMPLAINING`, `NONE`), otherwise the segment the purchase-journey stage maps to (`high`, `medium`, `low`), otherwise null. */\n purchase_intent?: \"BROWSING\" | \"RESEARCHING\" | \"COMPARING\" | \"READY_TO_BUY\" | \"POST_PURCHASE\" | \"COMPLAINING\" | \"NONE\" | \"high\" | \"medium\" | \"low\" | null | null;\n } | null;\n last_updated?: string | null;\n /** Present when screen is offline */\n message?: string;\n object?: string;\n request_id?: string;\n screen_id?: string;\n status?: \"live\" | \"offline\";\n};\n}\n\n/**\n * Find lookalike screens\n * GET /v1/partner/audience/lookalikes/{screenId}\n */\nexport interface getLookalikeScreensOperation {\n readonly path: \"/v1/partner/audience/lookalikes/{screenId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n country?: string;\n limit?: number;\n min_similarity?: number;\n venue_type?: string;\n};\n response: {\n filters?: {\n country?: string | null;\n min_similarity?: number;\n venue_type?: string | null;\n };\n matches?: Array<Record<string, unknown>>;\n matches_count?: number;\n object?: string;\n request_id?: string;\n source_screen_id?: string;\n};\n}\n\n/**\n * Predict screen audience\n * GET /v1/partner/audience/predict/{screenId}\n */\nexport interface predictScreenAudienceOperation {\n readonly path: \"/v1/partner/audience/predict/{screenId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n day: number;\n hour: number;\n lookback_days?: number;\n};\n response: {\n day_of_week?: number;\n hour_utc?: number;\n lookback_days?: number;\n /** Present when prediction is null */\n message?: string;\n object?: string;\n /** Null if insufficient historical data */\n prediction?: Record<string, unknown> | null;\n request_id?: string;\n screen_id?: string;\n};\n}\n\n/**\n * Resolved household reach for a campaign\n * GET /v1/partner/audience/resolved-household-reach/{campaignId}\n */\nexport interface getResolvedHouseholdReachOperation {\n readonly path: \"/v1/partner/audience/resolved-household-reach/{campaignId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n campaignId: string;\n};\n query: Record<string, unknown>;\n response: {\n audience_composition?: Array<Record<string, unknown>>;\n campaign_id?: string;\n confidence_interval?: {\n lower?: number;\n upper?: number;\n };\n households_reached?: number;\n overlap_with_first_party?: number | null;\n};\n}\n\n/**\n * List audience segments\n * GET /v1/partner/audience/segments\n */\nexport interface listAudienceSegmentsOperation {\n readonly path: \"/v1/partner/audience/segments\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: Array<AudienceSegment>;\n has_more?: boolean;\n object?: string;\n request_id?: string;\n total_count?: number;\n};\n}\n\n/**\n * Create audience segment\n * POST /v1/partner/audience/segments\n */\nexport interface createAudienceSegmentOperation {\n readonly path: \"/v1/partner/audience/segments\";\n readonly method: \"POST\";\n request: {\n description?: string;\n /** Segment name */\n name: string;\n rules: Array<{\n /** Rule field (e.g., venue_type, country, city, state) */\n field: string;\n /** Comparison operator (e.g., eq, neq, in, gt, lt) */\n operator: string;\n /** Rule value */\n value: unknown;\n }>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: AudienceSegment;\n}\n\n/**\n * Delete audience segment\n * DELETE /v1/partner/audience/segments/{segmentId}\n */\nexport interface deleteAudienceSegmentOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n segmentId: string;\n};\n query: Record<string, unknown>;\n response: {\n deleted?: boolean;\n id?: string;\n object?: string;\n request_id?: string;\n};\n}\n\n/**\n * Get audience segment\n * GET /v1/partner/audience/segments/{segmentId}\n */\nexport interface getAudienceSegmentOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n segmentId: string;\n};\n query: Record<string, unknown>;\n response: AudienceSegment;\n}\n\n/**\n * Update audience segment\n * PATCH /v1/partner/audience/segments/{segmentId}\n */\nexport interface updateAudienceSegmentOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}\";\n readonly method: \"PATCH\";\n request: {\n description?: string;\n is_active?: boolean;\n name?: string;\n rules?: Array<{\n field?: string;\n operator?: string;\n value?: unknown;\n }>;\n};\n params: {\n segmentId: string;\n};\n query: Record<string, unknown>;\n response: AudienceSegment;\n}\n\n/**\n * Match screens to segment\n * POST /v1/partner/audience/segments/{segmentId}/match\n */\nexport interface matchAudienceSegmentOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}/match\";\n readonly method: \"POST\";\n request: never;\n params: {\n segmentId: string;\n};\n query: {\n limit?: number;\n own_screens_only?: \"true\" | \"false\";\n};\n response: {\n matched_count?: number;\n matches?: Array<Record<string, unknown>>;\n object?: string;\n own_screens_only?: boolean;\n request_id?: string;\n segment_id?: string;\n segment_name?: string;\n};\n}\n\n/**\n * Estimate segment reach\n * POST /v1/partner/audience/segments/{segmentId}/reach\n */\nexport interface estimateSegmentReachOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}/reach\";\n readonly method: \"POST\";\n request: never;\n params: {\n segmentId: string;\n};\n query: {\n own_screens_only?: \"true\" | \"false\";\n};\n response: {\n country_breakdown?: Record<string, number>;\n estimated_daily_cpm?: number;\n estimated_daily_impressions?: number;\n estimated_daily_revenue?: number;\n has_historical_data?: boolean;\n matched_screens?: number;\n object?: string;\n online_screens?: number;\n segment_id?: string;\n segment_name?: string;\n venue_breakdown?: Record<string, number>;\n};\n}\n\n/**\n * Generate VAST tag for segment\n * POST /v1/partner/audience/segments/{segmentId}/vast-tag\n */\nexport interface generateSegmentVastTagOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}/vast-tag\";\n readonly method: \"POST\";\n request: {\n /** Click-through landing page URL */\n click_through?: string;\n /** Video creative URL */\n creative_url: string;\n /** Video duration in seconds */\n duration?: number;\n own_screens_only?: boolean;\n};\n params: {\n segmentId: string;\n};\n query: Record<string, unknown>;\n response: {\n creative?: {\n click_through?: string | null;\n duration?: number;\n url?: string;\n };\n cust_params?: Array<string>;\n object?: string;\n screen_ids?: Array<string>;\n segment_id?: string;\n segment_name?: string;\n targeted_screens?: number;\n vast_tag_url?: string;\n};\n}\n\n/**\n * Get audience data statistics\n * GET /v1/partner/audience/stats\n */\nexport interface getAudienceStatsOperation {\n readonly path: \"/v1/partner/audience/stats\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data_access_tier?: \"basic\" | \"developer\" | \"enterprise\";\n newest_data?: string | null;\n object?: string;\n oldest_data?: string | null;\n request_id?: string;\n screens_with_data?: number;\n total_screens?: number;\n total_vectors?: number;\n};\n}\n\n/**\n * Cancel the partner's subscription at period end\n * POST /v1/partner/billing/cancel\n */\nexport interface cancelBillingSubscriptionOperation {\n readonly path: \"/v1/partner/billing/cancel\";\n readonly method: \"POST\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n effective_at?: string;\n status?: \"canceled\" | \"scheduled_for_cancellation\";\n};\n}\n\n/**\n * Purchase committed-use credits\n * POST /v1/partner/billing/credits\n */\nexport interface purchaseCreditsOperation {\n readonly path: \"/v1/partner/billing/credits\";\n readonly method: \"POST\";\n request: {\n /** Credit purchase tier */\n tier: \"tier_500\" | \"tier_2000\" | \"tier_5000\";\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n /** Amount charged in cents */\n charged_cents?: number;\n /** Present when Stripe confirmation requires action; handle as a secret */\n client_secret?: string;\n /** Verified credit added in exact decimal cents; zero until capture settles */\n credits_added_cents?: string;\n /** Legacy field name: additional credit as a percentage of the charged amount */\n discount_pct?: number;\n object?: string;\n /** Durable purchase operation identifier */\n operation_id?: string;\n /** Stripe PaymentIntent ID */\n payment_intent_id?: string;\n request_id?: string;\n /** Complete Stripe confirmation before credit can settle */\n requires_action?: boolean;\n /** Native Stripe PaymentIntent status */\n status?: string;\n};\n}\n\n/**\n * Create Stripe Checkout session for credits\n * POST /v1/partner/billing/credits/checkout\n */\nexport interface createCreditCheckoutOperation {\n readonly path: \"/v1/partner/billing/credits/checkout\";\n readonly method: \"POST\";\n request: {\n /** URL to redirect if payment is cancelled */\n cancel_url?: string;\n /** URL to redirect after successful payment */\n success_url?: string;\n tier: \"tier_500\" | \"tier_2000\" | \"tier_5000\";\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n /** Stripe Checkout URL — redirect user here */\n checkout_url?: string;\n message?: string;\n object?: string;\n /** Durable credit-purchase operation identifier */\n operation_id?: string;\n request_id?: string;\n /** Stripe Checkout session ID */\n session_id?: string;\n};\n}\n\n/**\n * List available partner billing plans\n * GET /v1/partner/billing/plans\n */\nexport interface listBillingPlansOperation {\n readonly path: \"/v1/partner/billing/plans\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n plans?: Array<{\n included_ads?: number;\n included_impressions?: number;\n overage_per_1k_impressions_cents?: number;\n price_cents_monthly?: number;\n tier?: string;\n }>;\n};\n}\n\n/**\n * Set up usage-based billing\n * POST /v1/partner/billing/setup\n */\nexport interface setupBillingOperation {\n readonly path: \"/v1/partner/billing/setup\";\n readonly method: \"POST\";\n request: {\n /** Stripe payment method token obtained from Stripe.js or Stripe Elements */\n payment_method_id: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n billing_status?: string;\n message?: string;\n object?: string;\n request_id?: string;\n stripe_customer_id?: string;\n};\n}\n\n/**\n * Subscribe the partner to a billing plan\n * POST /v1/partner/billing/subscribe\n */\nexport interface createBillingSubscriptionOperation {\n readonly path: \"/v1/partner/billing/subscribe\";\n readonly method: \"POST\";\n request: {\n tier: \"developer\" | \"enterprise\";\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n checkout_url?: string | null;\n status?: \"active\" | \"requires_payment\";\n};\n}\n\n/**\n * Get the partner's current subscription\n * GET /v1/partner/billing/subscription\n */\nexport interface getBillingSubscriptionOperation {\n readonly path: \"/v1/partner/billing/subscription\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n current_period_end?: string;\n status?: \"active\" | \"past_due\" | \"canceled\" | \"trialing\";\n stripe_customer_id?: string | null;\n tier?: string;\n};\n}\n\n/**\n * Get the partner's current-period usage\n * GET /v1/partner/billing/usage\n */\nexport interface getBillingUsageOperation {\n readonly path: \"/v1/partner/billing/usage\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n ads_served?: number;\n audience_api_calls?: number;\n impressions?: number;\n overage_projection_cents?: number;\n period_end?: string;\n period_start?: string;\n};\n}\n\n/**\n * Get current billing period usage\n * GET /v1/partner/billing/usage-summary\n */\nexport interface getBillingUsageSummaryOperation {\n readonly path: \"/v1/partner/billing/usage-summary\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n billing_status: \"free\" | \"none\" | \"active\" | \"suspended\";\n /** Committed credit balance in cents, with eight decimal places */\n credit_balance_cents: string;\n /** Credit held by in-flight reservations, in cents with eight decimal places */\n credit_reserved_cents: string;\n object: string;\n /** Partner billing account identifier */\n partner_mongo_id: string;\n /** Exclusive end of the current UTC calendar month */\n period_end: string;\n period_start: string;\n products: Record<string, {\n meters: Record<string, {\n cost_cents: string;\n free_quantity: string;\n legacy_unknown_cents: string;\n paid_quantity: string;\n postpaid_cents: string;\n prepaid_cents: string;\n /** Total quantity in the published meter unit */\n total_quantity: string;\n }>;\n /** Product cost in cents with eight decimal places */\n total_cost_cents: string;\n }>;\n request_id: string;\n /** Period cost in cents with eight decimal places */\n total_cost_cents: string;\n};\n}\n\n/**\n * Stripe webhook callback\n * POST /v1/partner/billing/webhook\n */\nexport interface stripeWebhookCallbackOperation {\n readonly path: \"/v1/partner/billing/webhook\";\n readonly method: \"POST\";\n request: Record<string, unknown>;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: unknown;\n}\n\n/**\n * Get partner dashboard overview\n * GET /v1/partner/connect/dashboard\n */\nexport interface getConnectDashboardOperation {\n readonly path: \"/v1/partner/connect/dashboard\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n period?: \"7d\" | \"30d\" | \"90d\";\n};\n response: {\n data?: {\n charts?: {\n daily_impressions?: Array<{\n date?: string;\n earnings_cents?: number;\n impressions?: number;\n }>;\n hourly_today?: Array<{\n hour?: number;\n impressions?: number;\n }>;\n };\n earnings?: {\n available_cents?: number;\n daily_average_cents?: number;\n lifetime_cents?: number;\n pending_payout_cents?: number;\n period_cents?: number;\n today_cents?: number;\n };\n impressions?: {\n all_time?: number;\n daily_average?: number;\n period?: number;\n today?: number;\n trend_percent?: number;\n };\n partner?: {\n cpm_cents?: number;\n name?: string;\n revenue_share_percent?: number;\n slug?: string;\n status?: string;\n stripe_status?: \"pending\" | \"active\" | \"restricted\" | \"not_connected\";\n };\n period?: {\n days?: number;\n end?: string;\n start?: string;\n };\n screens?: {\n offline?: number;\n online?: number;\n pending?: number;\n total?: number;\n };\n top_screens?: Array<{\n earnings_cents?: number;\n impressions?: number;\n location?: string | null;\n name?: string;\n screen_id?: string;\n status?: \"online\" | \"offline\" | \"unknown\";\n }>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get earnings chart data\n * GET /v1/partner/connect/earnings\n */\nexport interface getConnectEarningsChartOperation {\n readonly path: \"/v1/partner/connect/earnings\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n group_by?: \"day\" | \"week\";\n period?: \"7d\" | \"30d\" | \"90d\";\n};\n response: {\n data?: {\n chart?: Array<{\n avg_view_duration_ms?: number;\n date?: string;\n earnings_cents?: number;\n impressions?: number;\n label?: string;\n unique_screens?: number;\n }>;\n period?: {\n days?: number;\n end?: string;\n start?: string;\n };\n summary?: {\n data_points?: number;\n group_by?: string;\n total_earnings_cents?: number;\n total_impressions?: number;\n };\n };\n success?: boolean;\n};\n}\n\n/**\n * Get screen health status\n * GET /v1/partner/connect/health\n */\nexport interface getConnectScreenHealthOperation {\n readonly path: \"/v1/partner/connect/health\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n status?: \"online\" | \"offline\" | \"all\";\n};\n response: {\n data?: {\n checked_at?: string;\n filter?: string;\n screens?: Array<{\n device_id?: string;\n external_device_id?: string;\n fingerprint?: string;\n last_heartbeat?: string | null;\n last_seen?: string | null;\n location?: {\n city?: string | null;\n lat?: number | null;\n lng?: number | null;\n state?: string | null;\n };\n name?: string;\n registered_at?: string;\n screen_id?: string | null;\n screen_status?: \"pending\" | \"active\" | \"inactive\";\n status?: \"online\" | \"offline\" | \"never_connected\" | \"unknown\";\n time_since_last_seen_seconds?: number | null;\n uptime_24h_percent?: number | null;\n venue_type?: string | null;\n }>;\n summary?: {\n never_connected?: number;\n offline?: number;\n online?: number;\n total?: number;\n };\n };\n success?: boolean;\n};\n}\n\n/**\n * List content presets\n * GET /v1/partner/content-presets\n */\nexport interface listContentPresetsOperation {\n readonly path: \"/v1/partner/content-presets\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: Array<ContentPreset>;\n has_more?: boolean;\n object?: string;\n total_count?: number;\n url?: string;\n};\n}\n\n/**\n * Get content preset\n * GET /v1/partner/content-presets/{presetId}\n */\nexport interface getContentPresetOperation {\n readonly path: \"/v1/partner/content-presets/{presetId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n presetId: string;\n};\n query: Record<string, unknown>;\n response: ContentPreset;\n}\n\n/**\n * Create an experiment on a funded native media-buy placement\n * POST /v1/partner/creative-experiments\n */\nexport interface createCreativeFeedbackExperimentOperation {\n readonly path: \"/v1/partner/creative-experiments\";\n readonly method: \"POST\";\n request: FeedbackCreateRequest;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: FeedbackExperimentResponse;\n}\n\n/**\n * Read your native experiment, policy and latest 500 owned creative recovery records\n * GET /v1/partner/creative-experiments/{experiment_id}\n */\nexport interface getCreativeFeedbackExperimentOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackExperimentDetailResponse;\n}\n\n/**\n * Configure your native brand profile and enable its booked reactive campaign\n * POST /v1/partner/creative-experiments/{experiment_id}/brand\n */\nexport interface configureCreativeFeedbackBrandOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/brand\";\n readonly method: \"POST\";\n request: FeedbackBrandRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackBrandResponse;\n}\n\n/**\n * Generate a native brand creative for explicit review and moderation\n * POST /v1/partner/creative-experiments/{experiment_id}/creatives/generate\n */\nexport interface generateCreativeFeedbackCreativeOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/creatives/generate\";\n readonly method: \"POST\";\n request: FeedbackGenerateRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackGenerateResponse;\n}\n\n/**\n * Import an AI model’s image through native bytes, moderation and brand review\n * POST /v1/partner/creative-experiments/{experiment_id}/creatives/import\n */\nexport interface importCreativeFeedbackCreativeOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/creatives/import\";\n readonly method: \"POST\";\n request: FeedbackImportRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackGenerateResponse;\n}\n\n/**\n * Approve or reject your generated creative using native brand review\n * POST /v1/partner/creative-experiments/{experiment_id}/creatives/review\n */\nexport interface reviewCreativeFeedbackCreativeOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/creatives/review\";\n readonly method: \"POST\";\n request: FeedbackReviewRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackReviewResponse;\n}\n\n/**\n * Replace native creative weights at the expected policy version\n * PUT /v1/partner/creative-experiments/{experiment_id}/policy\n */\nexport interface updateCreativeFeedbackPolicyOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/policy\";\n readonly method: \"PUT\";\n request: FeedbackPolicyRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackExperimentResponse;\n}\n\n/**\n * Read a metered page of latest measured native play states\n * GET /v1/partner/creative-experiments/{experiment_id}/rewards\n */\nexport interface getCreativeFeedbackRewardsOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/rewards\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: {\n /** Opaque next_cursor from this same date scope; rewind after scan_complete. */\n cursor?: string;\n /** Inclusive first play date, YYYY-MM-DD; date range is at most 31 days. */\n from: string;\n /** Unique caller-owned billing key for this page; committed retries return the receipt, not another read. */\n idempotency_key: string;\n /** Maximum native play states returned on this metered page. */\n limit?: number;\n /** Inclusive last play date, YYYY-MM-DD; date range is at most 31 days. */\n to: string;\n};\n response: FeedbackRewardsResponse;\n}\n\n/**\n * Stop the native experiment at the expected policy version\n * POST /v1/partner/creative-experiments/{experiment_id}/stop\n */\nexport interface stopCreativeFeedbackExperimentOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/stop\";\n readonly method: \"POST\";\n request: FeedbackStopRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackExperimentResponse;\n}\n\n/**\n * Register approved native generated creative variants\n * POST /v1/partner/creative-experiments/{experiment_id}/variants\n */\nexport interface registerCreativeFeedbackVariantsOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/variants\";\n readonly method: \"POST\";\n request: FeedbackRegisterVariantsRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackExperimentResponse;\n}\n\n/**\n * Submit a creative for classification\n * POST /v1/partner/creatives\n */\nexport interface submitPartnerCreativeOperation {\n readonly path: \"/v1/partner/creatives\";\n readonly method: \"POST\";\n request: {\n /** Optional advertiser/brand label to attach. */\n advertiser_name?: string;\n /** Public http(s) URL of the creative image or video. Must resolve to a public address — private/internal/cloud-metadata addresses are rejected. */\n media_url: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: CreativeClassification;\n}\n\n/**\n * Get a creative classification\n * GET /v1/partner/creatives/{ref}\n */\nexport interface getPartnerCreativeOperation {\n readonly path: \"/v1/partner/creatives/{ref}\";\n readonly method: \"GET\";\n request: never;\n params: {\n ref: string;\n};\n query: Record<string, unknown>;\n response: CreativeClassification;\n}\n\n/**\n * Get dashboard overview\n * GET /v1/partner/dashboard\n */\nexport interface getDashboardOperation {\n readonly path: \"/v1/partner/dashboard\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: DashboardResponse;\n success?: boolean;\n};\n}\n\n/**\n * Export the cross-screen proximity graph edge list\n * GET /v1/partner/data-export/proximity-graph-edges\n */\nexport interface exportProximityGraphEdgesOperation {\n readonly path: \"/v1/partner/data-export/proximity-graph-edges\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n cursor?: string;\n days?: number;\n limit?: number;\n};\n response: {\n data?: {\n limit: number;\n lookback_days: number;\n /** The `cursor` of the next page, or `null` on the last page. */\n next_cursor: string | null;\n rows: Array<{\n destination_screen_id: string;\n /** Distinct transitions from the source screen to the destination screen. */\n edge_count: number;\n /** Median minutes between the source and destination sightings. */\n lag_p50: number;\n source_screen_id: string;\n /** Distinct devices behind those transitions. */\n unique_devices: number;\n }>;\n /** Number of edges in this page. */\n total: number;\n /** `true` when more edges follow this page: pass `next_cursor` back as `cursor`. */\n truncated: boolean;\n /** The UTC second the window ends at (`YYYY-MM-DD HH:MM:SS`), pinned by the first page. */\n window_end: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * List available datasets and per-dataset record counts\n * GET /v1/partner/data-export/summary\n */\nexport interface getDataExportSummaryOperation {\n readonly path: \"/v1/partner/data-export/summary\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n days?: number;\n};\n response: DataExportSummaryResponse;\n}\n\n/**\n * Export a partner-scoped dataset\n * GET /v1/partner/data-export/{dataset}\n */\nexport interface exportDatasetOperation {\n readonly path: \"/v1/partner/data-export/{dataset}\";\n readonly method: \"GET\";\n request: never;\n params: {\n dataset: \"audience_metrics\" | \"signal_audience\" | \"attention_ledger\" | \"impression_ledger\" | \"waterfall_events\" | \"intent_signals\" | \"audience_loyalty_features\" | \"screen_metadata\";\n};\n query: {\n days?: number;\n limit?: number;\n offset?: number;\n};\n response: {\n data?: {\n lookback_days?: number;\n /** Row shape depends on the requested `dataset`. For `audience_loyalty_features` see `AudienceLoyaltyFeaturesRow`. For other datasets the row shape is documented per-dataset in the Trillboards data dictionary. */\n rows?: Array<AudienceLoyaltyFeaturesRow | Record<string, unknown>>;\n total?: number;\n truncated?: boolean;\n };\n success?: boolean;\n};\n}\n\n/**\n * Geographic audience panel (data-licensing)\n * GET /v1/partner/data/geohash-audience-panel\n */\nexport interface getGeohashAudiencePanelOperation {\n readonly path: \"/v1/partner/data/geohash-audience-panel\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n date?: string;\n geohash_prefix?: string;\n limit?: number;\n offset?: number;\n venue_category?: string;\n};\n response: {\n data?: {\n /** The build of the day this page read (UTC, `YYYY-MM-DD HH:MM:SS.mmm`). A day with no build is answered `404` `day_not_built`. */\n built_at: string;\n /** Day exported (UTC). */\n date: string;\n /** The panel's definition (2 since 2026-10-19). */\n definition_version: number;\n /** Rows per page this response was read with. */\n limit: number;\n /** The `offset` of the next page, or `null` on the last page. */\n next_offset: number | null;\n /** Rows skipped before this page. */\n offset: number;\n /** Integer partner_id (`partners.partner_id`). */\n partner_id: number;\n /** One row per (hour, geohash6, venue_category), every cell with its counts. */\n rows: Array<{\n /** Mean device head-pose index 0-1 over the camera windows. `null` when the cell-hour holds no camera window. */\n avg_attention: number | null;\n /** Mean dwell per window in milliseconds, each window's dwell capped at its own length, over the windows that carry a start time (dwell_capped_windows). `null`: No window of the row carries a start time (dwell_capped_windows = 0). */\n avg_dwell_ms: number | null;\n /** Mean capped dwell per camera window, seconds (`avg_dwell_ms` / 1000). `null` when no camera window of the cell-hour carries a start time. */\n avg_dwell_seconds: number | null;\n /** Mean faces per camera window. `null` when the cell-hour holds no camera window. */\n avg_face_count: number | null;\n /** The windows avg_dwell_ms averages: camera windows that carry a start time. */\n dwell_capped_windows: number;\n /** Of those, the face-backed windows. */\n face_backed_windows: number;\n /** Of those, the identifiers whose strongest signal was below it: the devices around the screen. Sources that report no signal strength (mDNS, SSDP, HTTP probe) are in neither split. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n far_key_slots: number | null;\n /** far_key_slots / radio_slots. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n far_keys_per_slot: number | null;\n /** Median age of those fixes at the window's instant. `null`: No camera window of the row was placed by a trail fix. */\n fix_age_p50_s: number | null;\n /** 90th-percentile age of those fixes. `null`: No camera window of the row was placed by a trail fix. */\n fix_age_p90_s: number | null;\n /** Camera windows of a fixed screen with no trail fix in the lookback, placed at its registered site. */\n fixed_site_positioned_windows: number;\n /** Geohash-6 cell (about 1.2 x 0.6 km) where the screen was at the unit's instant: on its map-matched path, else at its last GPS trail fix, else (a fixed screen) at its registered site. */\n geohash6: string;\n /** Start of the UTC hour, `YYYY-MM-DD HH:MM:SS`. A camera window counts in the hour it closed, a radio slot in the hour of its midpoint. */\n hour: string;\n /** `null` on every row: no income source joins the panel (definition 1 wrote `unknown`). */\n income_tier: string | null;\n /** Camera windows placed at the screen's last GPS fix at or before their instant. */\n last_fix_positioned_windows: number;\n /** The near-field boundary of the row's day: the active pass_audience method register's rf_near_rssi_dbm, the boundary the in-cab radio uses. */\n near_field_rssi_dbm: number;\n /** Of those, the identifiers whose strongest signal in the slot was at or above near_field_rssi_dbm: in the vehicle or against the screen. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n near_key_slots: number | null;\n /** near_key_slots / radio_slots: near-field identifiers concurrent in one slot. The approved metric radio_identifiers_per_person converts near-field passenger Bluetooth identifiers to people: it is measured in the cab, where the driver's phone and the vehicle's own devices are removed by role. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n near_keys_per_slot: number | null;\n /** Radio observations (pings) of those identifiers in the cell-hour (`radio_pings`). `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n observation_count: number | null;\n /** Camera-graded windows (face- or person-backed, ~10 s each) placed in the cell. */\n observation_windows: number;\n /** Exact 95th percentile of the per-window face reading over the row's windows. `null`: No camera window in the row. */\n p95_face_count: number | null;\n /** Camera windows placed on the map-matched vehicle path at their instant. */\n path_positioned_windows: number;\n /** Of those, the person-backed windows. */\n person_backed_windows: number;\n /** The windows persons_per_window averages. */\n person_reading_windows: number;\n /** Mean people in view per camera window, faced or not: per window the larger of its mean person count and its mean face count (the in-cab panel's persons reading), over the windows that carry one. `null`: No camera window of the row carries a persons reading (person_reading_windows = 0). */\n persons_per_window: number | null;\n /** Radio slots of a fixed screen with no trail fix in the lookback, placed at its registered site. */\n radio_fixed_site_positioned_slots: number;\n /** Radio slots placed at the screen's last GPS fix at or before their midpoint. */\n radio_last_fix_positioned_slots: number;\n /** Radio slots placed on the map-matched path at their midpoint. */\n radio_path_positioned_slots: number;\n /** Radio observations of the visitor identifiers in the row's slots. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n radio_pings: number | null;\n /** Distinct screens whose radio slots the row holds. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n radio_screens: number | null;\n /** 6-minute radio slots of screens placed in the cell: a slot in which the screen's visitor radio read an identifier (a slot in which the scanner reported only the screen's own Wi-Fi access point does not show the visitor radio ran, and is no slot here). 0 when only camera windows fell in the row. */\n radio_slots: number;\n /** Distinct screens whose radio slots the cell-hour holds (`radio_screens`). `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n screen_count: number | null;\n /** Distinct screens whose camera windows the row holds. */\n screens: number;\n /** Distinct visitor-source radio identifiers (BLE, mDNS, SSDP, HTTP probe) over the cell-hour's radio slots, exact. A distinct count: rows do not add. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n unique_devices: number | null;\n /** The screen's registered venue category (`transit` for a vehicle screen); empty when the registry holds none. */\n venue_category: string;\n /** Visitor-source radio identifiers (BLE, mDNS, SSDP, HTTP probe) distinct within each slot, summed over the slots. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n visitor_key_slots: number | null;\n /** visitor_key_slots / radio_slots: identifiers concurrent in one 6-minute slot, every visitor source and both fields. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n visitor_keys_per_slot: number | null;\n }>;\n /** `true` when more cells follow this page: request `next_offset`. */\n truncated: boolean;\n };\n success?: boolean;\n};\n}\n\n/**\n * Outcome-attribution export (data-licensing)\n * GET /v1/partner/data/outcome-attribution\n */\nexport interface getOutcomeAttributionOperation {\n readonly path: \"/v1/partner/data/outcome-attribution\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n creative_id?: string;\n cursor?: string;\n days?: number;\n grain?: \"person\" | \"device\";\n limit?: number;\n};\n response: {\n data?: {\n /** The reach grain applied (echoes the `grain` query param; defaults to `person`). */\n grain: \"person\" | \"device\";\n /** Rows per page this response was read with. */\n limit: number;\n /** The validated lookback window used. */\n lookback_days: number;\n /** The `cursor` of the next page, or `null` on the last page. */\n next_cursor: string | null;\n /** Per-(creative, screen) outcome aggregates, every cell with its counts. */\n rows: Array<{\n /** Average exposures per reach-identity within the (creative, screen) cell (count of detail rows / distinct reach keys). A frequency over PEOPLE under person grain, over devices under device grain. */\n avg_frequency: number;\n /** Creative identifier from `completed_impressions.creative_id`. */\n creative_id: string;\n /** Count of exposed identities also seen on another screen. */\n cross_screen_devices: number;\n /** Count of distinct devices exposed to the creative on the screen. */\n exposed_devices: number;\n /** returned_devices / exposed_devices. */\n return_rate: number;\n /** Count of exposed devices that returned (≤ exposed_devices). */\n returned_devices: number;\n /** The partner's own screen identifier. */\n screen_mongo_id: string;\n }>;\n /** Number of rows in this page. */\n total: number;\n /** `true` when more cells follow this page: pass `next_cursor` back as `cursor`. */\n truncated: boolean;\n /** The UTC date the window ends on, pinned by the first page; the window is `window_end - lookback_days` through `window_end`. */\n window_end: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Register a device\n * POST /v1/partner/device\n */\nexport interface registerDeviceOperation {\n readonly path: \"/v1/partner/device\";\n readonly method: \"POST\";\n request: {\n /** Any additional metadata for your records */\n custom_metadata?: Record<string, unknown>;\n /** Screen or venue description for enrichment */\n description?: string;\n /** Your internal device identifier */\n device_id: string;\n /** Mutable partner category; defaults to other only on a new registration. Omit it to preserve an existing device's category. */\n device_type?: \"tv\" | \"tablet\" | \"display\" | \"kiosk\" | \"other\" | \"mobile\" | \"connected_tv\" | \"digital_signage\" | \"vending_machine\";\n /** Screen display specification. `width`/`height` are pixel resolution; `width_in`/`height_in` are physical inches. For accurate floor pricing we need physical inches — pass `width_in` + `height_in` if you've measured the screen, OR `ppi` so the server can derive inches from the pixel resolution. Pixel-only inputs (no `ppi`, no `*_in`) will NOT be recorded as physical size; they're still used for orientation detection and DSP slot dimensions. */\n display?: {\n /** Display resolution height in pixels (e.g. 1080) */\n height?: number;\n /** Physical screen height in INCHES. Pairs with `width_in`. */\n height_in?: number;\n orientation?: \"landscape\" | \"portrait\";\n /** Pixels per inch. Required if you want physical screen size derived from `width`/`height` (server computes widthInches = width / ppi). Typical: 40 (large TV), 96 (desktop monitor), 264 (tablet). */\n ppi?: number;\n /** Display resolution width in pixels (e.g. 1920) */\n width?: number;\n /** Physical screen width in INCHES. Overrides ppi-based derivation. Use this when you've measured the actual screen (most accurate for pricing). */\n width_in?: number;\n };\n display_settings?: PartnerRegistrationDisplaySettings;\n location?: {\n address?: string;\n city?: string;\n country?: string;\n lat?: number;\n lng?: number;\n state?: string;\n timezone?: string;\n venue_name?: string;\n venue_type?: \"mall\" | \"airport\" | \"retail\" | \"restaurant\" | \"office\" | \"gym\" | \"hospital\" | \"school\" | \"other\";\n zip?: string;\n };\n /** Reporting hints for moving screens (mobility_type mobile/semi_mobile). */\n mobile_settings?: {\n /** Minimum displacement before a fix is sent (device-side hint). */\n distance_threshold_meters?: number;\n /** Target GPS reporting interval (floor — the platform adapts to speed/battery). */\n update_interval_seconds?: number;\n vehicle_type?: \"taxi\" | \"rideshare\" | \"delivery\" | \"bus\" | \"food_truck\" | \"other\";\n };\n /** Declare a moving screen. `mobile` (taxi-top, transit) and `semi_mobile` (food truck, pop-up) screens report GPS via the heartbeat `location` field; `static` (default) screens ignore it. */\n mobility_type?: \"static\" | \"mobile\" | \"semi_mobile\";\n /** Friendly name for the device */\n name?: string;\n /** Photo URL for enrichment context */\n photo_url?: string;\n /** Optional additional photos for enrichment */\n photos?: Array<{\n /** photo type (context, close, angle, other) */\n type?: string;\n url?: string;\n }>;\n /** Physical placement details (optional) */\n placement?: {\n height_from_floor_ft?: number;\n mounting_type?: \"wall\" | \"ceiling\" | \"tabletop\" | \"window\" | \"stand\" | \"freestanding\" | \"floor_stand\" | \"counter\" | \"pole\" | \"vehicle\" | \"other\" | \"unknown\";\n orientation?: \"landscape\" | \"portrait\";\n viewing_distance_ft?: number;\n };\n /** Venue classification details (optional). Subcategory is free text and may be auto-enriched by AI. */\n venue?: {\n business_name?: string;\n category?: \"retail\" | \"restaurant\" | \"bar\" | \"cafe\" | \"hotel\" | \"office\" | \"gym\" | \"hospital\" | \"school\" | \"mall\" | \"airport\" | \"gas_station\" | \"convenience_store\" | \"laundromat\" | \"salon\" | \"auto_shop\" | \"other\";\n context_notes?: string;\n environment_type?: \"indoor\" | \"outdoor\" | \"semi_outdoor\" | \"transit\";\n placement_type?: \"digital_signage\" | \"point_of_sale\" | \"waiting_area\" | \"window_display\" | \"kiosk\" | \"other\";\n /** Free text subcategory (may be auto-enriched by AI) */\n subcategory?: string;\n };\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Our internal device ID */\n device_id?: string;\n /** Saved partner category, independent of advertising classification. */\n device_type?: \"tv\" | \"tablet\" | \"display\" | \"kiosk\" | \"other\" | \"mobile\" | \"connected_tv\" | \"digital_signage\" | \"vending_machine\";\n /** Load this URL in your WebView */\n embed_url?: string;\n /** Enrichment completion state */\n enrichment_status?: string;\n /** Your device ID */\n external_device_id?: string;\n /** Device fingerprint for SDK */\n fingerprint?: string;\n location_capability?: PartnerLocationCapability;\n /** Stripe-style readiness. `ready` — the screen has its own coordinates and its venue is classified. `limited` — servable, but not on every rail: the venue is not yet classified, or the screen has no position of its own and is listed in inventory feeds at your fleet's market location. `blocked` — no rail has a location, or classification permanently failed. A device is never refused registration for missing location. */\n monetization_status?: \"ready\" | \"limited\" | \"blocked\";\n requirements?: PartnerMonetizationRequirements;\n /** Linked screen ID */\n screen_id?: string;\n status?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Delete a device\n * DELETE /v1/partner/device/{deviceId}\n */\nexport interface deleteDeviceOperation {\n readonly path: \"/v1/partner/device/{deviceId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get device info\n * GET /v1/partner/device/{deviceId}\n */\nexport interface getDeviceOperation {\n readonly path: \"/v1/partner/device/{deviceId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: Device;\n success?: boolean;\n};\n}\n\n/**\n * Update device metadata\n * PATCH /v1/partner/device/{deviceId}\n */\nexport interface updateDeviceOperation {\n readonly path: \"/v1/partner/device/{deviceId}\";\n readonly method: \"PATCH\";\n request: {\n /** Any additional metadata for your records */\n custom_metadata?: Record<string, unknown>;\n /** Screen or venue description */\n description?: string;\n /** Mutable partner category. Omit it to preserve the current value; changing it does not alter advertising classification. */\n device_type?: \"tv\" | \"tablet\" | \"display\" | \"kiosk\" | \"other\" | \"mobile\" | \"connected_tv\" | \"digital_signage\" | \"vending_machine\";\n /** Screen display specification. `width`/`height` are pixels; pass `width_in`/`height_in` (inches) or `ppi` to record physical screen size for floor pricing. See POST /device for full unit semantics. */\n display?: {\n /** Display resolution height in pixels */\n height?: number;\n /** Physical screen height in inches (pairs with width_in) */\n height_in?: number;\n orientation?: \"landscape\" | \"portrait\";\n /** Pixels per inch (required to derive physical size from width/height) */\n ppi?: number;\n /** Display resolution width in pixels */\n width?: number;\n /** Physical screen width in inches (overrides ppi-based derivation) */\n width_in?: number;\n };\n /** Partner-owned device ID used for idempotent lookup and reconciliation */\n external_device_id?: string;\n location?: {\n address?: string;\n city?: string;\n country?: string;\n lat?: number;\n lng?: number;\n state?: string;\n timezone?: string;\n /** Alias for `venue.category` — a granular venue type. Accepted on PATCH (previously documented on register only). When both are sent, `venue.category` wins. On the response this field returns the OpenOOH CHILD slug (e.g. \"hotels\"/\"malls\"); `venue.category` returns the parent. */\n venue_type?: \"mall\" | \"airport\" | \"retail\" | \"restaurant\" | \"office\" | \"gym\" | \"hospital\" | \"school\" | \"other\";\n zip?: string;\n };\n /** Reporting hints for moving screens. Merges with existing settings. */\n mobile_settings?: {\n distance_threshold_meters?: number;\n update_interval_seconds?: number;\n vehicle_type?: \"taxi\" | \"rideshare\" | \"delivery\" | \"bus\" | \"food_truck\" | \"other\";\n };\n /** Update the screen's mobility. Set `mobile`/`semi_mobile` to have the heartbeat `location` field accepted for a moving screen. */\n mobility_type?: \"static\" | \"mobile\" | \"semi_mobile\";\n /** Friendly name for the device */\n name?: string;\n photo_url?: string;\n photos?: Array<{\n type?: string;\n url?: string;\n }>;\n placement?: {\n height_from_floor_ft?: number;\n mounting_type?: \"wall\" | \"ceiling\" | \"tabletop\" | \"window\" | \"stand\" | \"freestanding\" | \"floor_stand\" | \"counter\" | \"pole\" | \"vehicle\" | \"other\" | \"unknown\";\n orientation?: \"landscape\" | \"portrait\";\n viewing_distance_ft?: number;\n };\n venue?: {\n business_name?: string;\n /** Granular venue type on WRITE (child-level slug). On READ this returns the resolved OpenOOH PARENT (e.g. \"hotel\" write -> \"entertainment\" read); the child slug rides `venue.subcategory` and the numeric child id rides `venue.openooh_venue_type_id`. */\n category?: \"retail\" | \"restaurant\" | \"bar\" | \"cafe\" | \"hotel\" | \"office\" | \"gym\" | \"hospital\" | \"school\" | \"mall\" | \"airport\" | \"gas_station\" | \"convenience_store\" | \"laundromat\" | \"salon\" | \"auto_shop\" | \"other\";\n context_notes?: string;\n environment_type?: \"indoor\" | \"outdoor\" | \"semi_outdoor\" | \"transit\";\n /** Response-only. Canonical OpenOOH child id to reconcile the declared venue type against (e.g. 807 for hotels). */\n openooh_venue_type_id?: number;\n placement_type?: \"digital_signage\" | \"point_of_sale\" | \"waiting_area\" | \"window_display\" | \"kiosk\" | \"other\";\n /** OpenOOH child slug (e.g. \"hotels\"). Free text on write; resolved on read. */\n subcategory?: string;\n };\n};\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: Device;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get ads for device\n * GET /v1/partner/device/{deviceId}/ads\n */\nexport interface getAdsForDeviceOperation {\n readonly path: \"/v1/partner/device/{deviceId}/ads\";\n readonly method: \"GET\";\n request: never;\n params: {\n deviceId: string;\n};\n query: {\n autoplay?: boolean;\n ipd?: string;\n lang?: string;\n muted?: boolean;\n orientation?: \"portrait\" | \"landscape\";\n slot_h?: number;\n slot_w?: number;\n test?: \"1\" | \"true\";\n ua?: string;\n};\n response: {\n data?: {\n ads?: Array<Ad>;\n cache_until?: string;\n /** Pending device commands to execute (remote management). */\n commands?: Array<{\n command?: string;\n id?: string;\n payload?: Record<string, unknown>;\n queued_at?: string;\n }>;\n /** Venue/content keywords for contextual targeting. Omitted from the response when empty. */\n content_keywords?: Array<string>;\n device?: {\n display?: {\n height?: number | null;\n width?: number | null;\n };\n fingerprint?: string;\n };\n /** DOOH context signals (venue type, geo, audience) forwarded to demand. */\n dooh_signals?: Record<string, unknown>;\n header_bidding_settings?: HeaderBiddingSettings;\n /** URL of the Trillboards-hosted IMA renderer for this screen, or null when the IMA player is not enabled for this deployment / there is no waterfall source. */\n ima_player_url?: string | null;\n screen_dimensions?: {\n height?: number;\n orientation?: \"portrait\" | \"landscape\";\n width?: number;\n };\n screen_id?: string;\n /** Serve locale derived from the venue country (e.g. \"en\", \"fr\"). */\n screen_locale?: string;\n screen_name?: string | null;\n screen_orientation?: \"portrait\" | \"landscape\";\n settings?: AdSettings;\n };\n success?: boolean;\n};\n}\n\n/**\n * Acknowledge a device command\n * POST /v1/partner/device/{deviceId}/commands/{commandId}/ack\n */\nexport interface ackDeviceCommandOperation {\n readonly path: \"/v1/partner/device/{deviceId}/commands/{commandId}/ack\";\n readonly method: \"POST\";\n request: {\n /** Command execution result */\n result?: Record<string, unknown>;\n /** Acknowledgment status */\n status?: string;\n};\n params: {\n commandId: string;\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Read live device heartbeat state\n * GET /v1/partner/device/{deviceId}/heartbeat\n */\nexport interface getDeviceHeartbeatOperation {\n readonly path: \"/v1/partner/device/{deviceId}/heartbeat\";\n readonly method: \"GET\";\n request: never;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: PartnerHeartbeatQueryResponse;\n}\n\n/**\n * Device heartbeat\n * POST /v1/partner/device/{deviceId}/heartbeat\n */\nexport interface heartbeatOperation {\n readonly path: \"/v1/partner/device/{deviceId}/heartbeat\";\n readonly method: \"POST\";\n request: PartnerHeartbeatRequest | PartnerDeviceHeartbeatBody;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: PartnerHeartbeatResponse;\n}\n\n/**\n * Update a device's publisher impression multiplier\n * PATCH /v1/partner/device/{deviceId}/impression-multiplier\n */\nexport interface updateDeviceImpressionMultiplierOperation {\n readonly path: \"/v1/partner/device/{deviceId}/impression-multiplier\";\n readonly method: \"PATCH\";\n request: {\n /** Publisher-declared audience impressions per ad play; stored to four decimal places */\n impression_multiplier: number;\n};\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n device_id?: string;\n external_device_id?: string;\n impression_multiplier?: number;\n screen_id?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Report a rider interaction from the partner SDK (reportInteraction)\n * POST /v1/partner/device/{deviceId}/interactions\n */\nexport interface recordPartnerDeviceInteractionOperation {\n readonly path: \"/v1/partner/device/{deviceId}/interactions\";\n readonly method: \"POST\";\n request: PartnerInteraction;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data: {\n interaction_id: string;\n /** The screen this device is bound to. */\n screen_id: string;\n status: \"accepted\" | \"duplicate\";\n type: \"tap\" | \"qr_shown\" | \"qr_scanned\" | \"dwell_prompt\" | \"seatbelt_reminder_shown\";\n };\n success: true;\n};\n}\n\n/**\n * Report a play from the partner SDK (reportPlay)\n * POST /v1/partner/device/{deviceId}/plays\n */\nexport interface recordPartnerDevicePlayOperation {\n readonly path: \"/v1/partner/device/{deviceId}/plays\";\n readonly method: \"POST\";\n request: PartnerDevicePlay;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data: {\n content_sha256: string;\n play_id: string;\n /** The screen this device is bound to. */\n screen_id: string;\n status: \"accepted\" | \"duplicate\";\n };\n success: true;\n};\n}\n\n/**\n * Record programmatic ad event (Server-side Analytics)\n * POST /v1/partner/device/{deviceId}/programmatic-event\n */\nexport interface recordProgrammaticEventOperation {\n readonly path: \"/v1/partner/device/{deviceId}/programmatic-event\";\n readonly method: \"POST\";\n request: {\n ad_id?: string;\n error_code?: string;\n error_message?: string;\n event: \"ad_started\" | \"ad_ended\" | \"no_fill\" | \"error\";\n reason?: string;\n request_id?: string;\n screen_orientation?: \"portrait\" | \"landscape\";\n slot_height?: number;\n slot_width?: number;\n variant_name?: string;\n};\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Report a taxi-meter state change from the partner SDK (setTripState)\n * POST /v1/partner/device/{deviceId}/trips\n */\nexport interface recordPartnerDeviceTripStateOperation {\n readonly path: \"/v1/partner/device/{deviceId}/trips\";\n readonly method: \"POST\";\n request: PartnerTripState;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data: {\n event_seq: number;\n /** The screen this device is bound to. */\n screen_id: string;\n status: \"accepted\" | \"duplicate\";\n trip_id: string;\n };\n success: true;\n};\n}\n\n/**\n * Get fresh VAST for a registered device\n * GET /v1/partner/device/{deviceId}/vast\n */\nexport interface getVastForDeviceOperation {\n readonly path: \"/v1/partner/device/{deviceId}/vast\";\n readonly method: \"GET\";\n request: never;\n params: {\n deviceId: string;\n};\n query: {\n autoplay?: boolean;\n connection_type?: string;\n content_id?: string;\n device_memory?: number;\n dpr?: number;\n ipd?: string;\n lang?: string;\n make?: string;\n model?: string;\n muted?: boolean;\n orientation?: \"portrait\" | \"landscape\";\n os?: string;\n platform_hint?: string;\n screen_h?: number;\n screen_w?: number;\n sdk_type?: string;\n sdk_version?: string;\n slot_h?: number;\n slot_w?: number;\n stream_type?: string;\n test?: \"1\" | \"true\";\n ua?: string;\n vp_h?: number;\n vp_w?: number;\n};\n response: unknown;\n}\n\n/**\n * List all devices\n * GET /v1/partner/devices\n */\nexport interface listDevicesOperation {\n readonly path: \"/v1/partner/devices\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n offset?: number;\n status?: \"pending\" | \"active\" | \"offline\" | \"suspended\";\n};\n response: {\n data?: {\n devices?: Array<DeviceSummary>;\n limit?: number;\n offset?: number;\n total?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Register a device (V2 single-call onboarding)\n * POST /v1/partner/devices\n */\nexport interface registerDeviceV2Operation {\n readonly path: \"/v1/partner/devices\";\n readonly method: \"POST\";\n request: {\n /** Optional display dimensions. One of three modes — the `mode` discriminator is required. */\n display?: {\n /** Physical screen height in INCHES. Pairs with `width_in`. */\n height_in: number;\n mode: \"inches\";\n /** Physical screen width in INCHES. No upper bound — large-format / billboard inventory is valid. */\n width_in: number;\n } | {\n height: number;\n mode: \"pixels_with_ppi\";\n ppi: number;\n width: number;\n } | {\n height: number;\n mode: \"pixels_only\";\n width: number;\n };\n /** Your stable per-device identifier (e.g. \"REC00005\"). */\n external_id: string;\n /** Optional hardware identity — improves DSP device matching. */\n hardware?: {\n make?: string;\n model?: string;\n os?: \"Android\" | \"iOS\" | \"Tizen\" | \"WebOS\" | \"Linux\" | \"FireOS\" | \"Other\";\n osv?: string;\n };\n /** Optional venue location — either lat/lng coordinates or a street address. */\n location?: {\n lat: number;\n lng: number;\n } | {\n address: string;\n };\n /** The device's real WebView User-Agent. Must include a Build fingerprint and be unique per device. */\n ua: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n device_id?: string;\n diagnostics_url?: string;\n /** Paste into an iframe to start serving. */\n embed_url?: string;\n /** Server-derived canonical device identity. */\n identity?: {\n derivation_confidence?: number | null;\n derivation_source?: string | null;\n device_class?: string | null;\n make?: string | null;\n model?: string | null;\n os?: string | null;\n osv?: string | null;\n };\n };\n success?: boolean;\n};\n}\n\n/**\n * Bulk register devices\n * POST /v1/partner/devices/batch\n */\nexport interface registerDevicesBatchOperation {\n readonly path: \"/v1/partner/devices/batch\";\n readonly method: \"POST\";\n request: {\n /** Default fields applied to each device */\n defaults?: {\n display_settings?: PartnerRegistrationDisplaySettings;\n };\n devices: Array<{\n device_id: string;\n device_type?: string;\n display?: Record<string, unknown>;\n display_settings?: PartnerRegistrationDisplaySettings;\n location?: Record<string, unknown>;\n name?: string;\n placement?: Record<string, unknown>;\n venue?: Record<string, unknown>;\n }>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n devices?: Array<{\n device_id?: string;\n fingerprint?: string;\n location_capability?: PartnerLocationCapability;\n monetization_status?: \"ready\" | \"limited\" | \"blocked\";\n requirements?: PartnerMonetizationRequirements;\n screen_id?: string;\n status?: string;\n trillboard_id?: string;\n }>;\n errors?: Array<{\n device_id?: string;\n error?: string;\n /** Stable code, e.g. DEVICE_ID_CLAIMED (409 — a device that already reports to Trillboards under this id is registered to another account), REGISTRATION_CHECK_UNAVAILABLE (503 — retry), INVALID_COUNTRY (400). */\n error_code?: string;\n status?: string;\n }>;\n success?: boolean;\n summary?: {\n created?: number;\n failed?: number;\n total?: number;\n updated?: number;\n };\n};\n}\n\n/**\n * Batch update screen orientation\n * POST /v1/partner/devices/batch/orientation\n */\nexport interface batchUpdateDeviceOrientationOperation {\n readonly path: \"/v1/partner/devices/batch/orientation\";\n readonly method: \"POST\";\n request: {\n device_ids: Array<string>;\n screen_orientation: \"portrait\" | \"landscape\" | \"mixed\" | \"unknown\";\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n matched_devices?: number;\n missing_device_ids?: Array<string>;\n success?: boolean;\n total_devices?: number;\n updated_screens?: number;\n};\n}\n\n/**\n * Reconcile device fleet\n * POST /v1/partner/devices/reconcile\n */\nexport interface reconcileDevicesOperation {\n readonly path: \"/v1/partner/devices/reconcile\";\n readonly method: \"POST\";\n request: {\n /** Your device external IDs to reconcile. */\n external_ids: Array<string>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n duplicate?: Array<Record<string, unknown>>;\n malformed?: Array<Record<string, unknown>>;\n matched?: Array<Record<string, unknown>>;\n missing?: Array<string>;\n orphan?: Array<Record<string, unknown>>;\n success?: boolean;\n summary?: {\n duplicate?: number;\n malformed?: number;\n matched?: number;\n missing?: number;\n orphan?: number;\n registered_active?: number;\n requested?: number;\n };\n truncated?: Record<string, unknown>;\n};\n}\n\n/**\n * Browsable HTML documentation index\n * GET /v1/partner/docs\n */\nexport interface partnerDocsBrowserOperation {\n readonly path: \"/v1/partner/docs\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: unknown;\n}\n\n/**\n * Get earnings summary\n * GET /v1/partner/earnings\n */\nexport interface getEarningsOperation {\n readonly path: \"/v1/partner/earnings\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: EarningsResponse;\n success?: boolean;\n};\n}\n\n/**\n * Get detailed earnings breakdown\n * GET /v1/partner/earnings/breakdown\n */\nexport interface getEarningsBreakdownOperation {\n readonly path: \"/v1/partner/earnings/breakdown\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n end_date?: string;\n group_by?: \"day\" | \"week\" | \"screen\";\n start_date?: string;\n};\n response: {\n data?: EarningsBreakdownResponse;\n success?: boolean;\n};\n}\n\n/**\n * Request a payout\n * POST /v1/partner/earnings/payout\n */\nexport interface requestPayoutOperation {\n readonly path: \"/v1/partner/earnings/payout\";\n readonly method: \"POST\";\n request: {\n /** Amount to payout in cents (optional, defaults to available balance) */\n amount_cents?: number;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: PayoutResponse;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get transaction history\n * GET /v1/partner/earnings/transactions\n */\nexport interface getTransactionsOperation {\n readonly path: \"/v1/partner/earnings/transactions\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n offset?: number;\n type?: \"all\" | \"earning\" | \"payout\";\n};\n response: {\n data?: {\n pagination?: Pagination;\n transactions?: Array<Transaction>;\n };\n success?: boolean;\n};\n}\n\n/**\n * List active SSE connections\n * GET /v1/partner/events/connections\n */\nexport interface getActiveSSEConnectionsOperation {\n readonly path: \"/v1/partner/events/connections\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n connections?: Array<{\n connected_at?: string;\n connection_id?: string;\n ip?: string;\n subscribed_events?: Array<string>;\n }>;\n count?: number;\n max_connections?: number;\n object?: string;\n};\n}\n\n/**\n * Subscribe to real-time events (SSE)\n * GET /v1/partner/events/stream\n */\nexport interface subscribeToEventsOperation {\n readonly path: \"/v1/partner/events/stream\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n events?: string;\n};\n response: unknown;\n}\n\n/**\n * Poll a fleet-command job\n * GET /v1/partner/fleet-commands/{id}\n */\nexport interface getFleetCommandOperation {\n readonly path: \"/v1/partner/fleet-commands/{id}\";\n readonly method: \"GET\";\n request: never;\n params: {\n id: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n fleet_command?: FleetCommand;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get fleet analytics\n * GET /v1/partner/fleet/analytics\n */\nexport interface getFleetAnalyticsOperation {\n readonly path: \"/v1/partner/fleet/analytics\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Average attention score (0-1) over the fleet's audience windows in the last 24 hours. Omitted when it was not measured — no screen in scope had an audience window, or the audience read failed (see `avg_attention_outcome`); never a `0` that stands in for a missing measurement. */\n avg_attention?: number;\n /** Whether the attention average above was measured. `kind: ok` — the read ran (the average is omitted only when no screen had an audience window); `kind: unavailable` — the audience read failed, so the average is omitted. Independent of the other fields, which are served either way. */\n avg_attention_outcome?: {\n kind?: \"ok\" | \"unavailable\";\n message?: string;\n };\n /** Average CPM in dollars */\n avg_cpm?: number;\n /** Fill rate percentage (0-100) */\n fill_rate_pct?: number;\n impressions_24h?: number;\n screen_count?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Push command to fleet\n * POST /v1/partner/fleet/command\n */\nexport interface pushFleetCommandOperation {\n readonly path: \"/v1/partner/fleet/command\";\n readonly method: \"POST\";\n request: {\n command: {\n /** Command-specific data */\n payload?: Record<string, unknown>;\n type: \"reload\" | \"update_settings\" | \"restart\" | \"screenshot\" | \"clear_cache\" | \"update_content\" | \"refresh_ads\" | \"update_config\" | \"get_state\" | \"show_ad\" | \"play_ad\";\n };\n /** Optional filter to target specific devices */\n filter?: {\n country?: string;\n /** A screen group id (sg_) whose members are targeted (unioned with screenIds). */\n group?: string;\n screenIds?: Array<string>;\n venueType?: string;\n };\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n errors?: Array<{\n error?: string;\n screenId?: string;\n }>;\n /** Devices successfully queued */\n queued?: number;\n /** Total devices matching filter */\n targeted?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get fleet overview\n * GET /v1/partner/fleet/overview\n */\nexport interface getFleetOverviewOperation {\n readonly path: \"/v1/partner/fleet/overview\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n countries?: Record<string, number>;\n offline?: number;\n online?: number;\n programmatic_enabled?: number;\n total_devices?: number;\n venue_types?: Record<string, number>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Update fleet-wide settings\n * POST /v1/partner/fleet/settings\n */\nexport interface updateFleetSettingsOperation {\n readonly path: \"/v1/partner/fleet/settings\";\n readonly method: \"POST\";\n request: {\n filter?: {\n country?: string;\n /** A screen group id (sg_) whose members are targeted (unioned with screenIds). */\n group?: string;\n screenIds?: Array<string>;\n venueType?: string;\n };\n /** Settings to update (merged with existing) */\n settings: {\n content_preferences?: Record<string, unknown>;\n display_settings?: Record<string, unknown>;\n header_bidding_settings?: {\n allocation?: number;\n enabled?: boolean;\n force_request?: boolean;\n min_interval_seconds?: number;\n };\n };\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n errors?: Array<Record<string, unknown>>;\n targeted?: number;\n updated?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Record impression (pixel)\n * GET /v1/partner/impression\n */\nexport interface recordImpressionGetOperation {\n readonly path: \"/v1/partner/impression\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n adid: string;\n aid?: string;\n completed?: boolean;\n did?: string;\n display_area_ratio?: number;\n display_mode?: \"fullscreen\" | \"l-bar\" | \"pip\";\n duration?: number;\n impid: string;\n lbar_position?: \"bottom\" | \"right\";\n lbar_size?: number;\n lbar_slot_count?: number;\n lbar_slot_index?: number;\n sid?: string;\n sig?: string;\n sig_ts?: number;\n};\n response: {\n duplicate?: boolean;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Record impression (POST)\n * POST /v1/partner/impression\n */\nexport interface recordImpressionPostOperation {\n readonly path: \"/v1/partner/impression\";\n readonly method: \"POST\";\n request: {\n adid: string;\n aid?: string;\n completed?: boolean;\n did?: string;\n display_area_ratio?: number;\n display_mode?: \"fullscreen\" | \"l-bar\" | \"pip\";\n duration?: number;\n impid: string;\n lbar_position?: \"bottom\" | \"right\";\n lbar_size?: number;\n lbar_slot_count?: number;\n lbar_slot_index?: number;\n sid?: string;\n sig?: string;\n sig_ts?: number;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n duplicate?: boolean;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Batch record impressions\n * POST /v1/partner/impressions/batch\n */\nexport interface recordImpressionsBatchOperation {\n readonly path: \"/v1/partner/impressions/batch\";\n readonly method: \"POST\";\n request: {\n impressions: Array<{\n adid: string;\n aid?: string;\n completed?: boolean;\n device_fingerprint?: string;\n did?: string;\n display_area_ratio?: number;\n display_mode?: \"fullscreen\" | \"l-bar\" | \"pip\";\n duration?: number;\n impid: string;\n lbar_position?: \"bottom\" | \"right\";\n lbar_size?: number;\n lbar_slot_count?: number;\n lbar_slot_index?: number;\n sid?: string;\n sig?: string;\n sig_ts?: number;\n timestamp?: string;\n }>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n errors?: Array<{\n error?: string;\n impid?: string;\n }>;\n failed?: number;\n recorded?: number;\n skipped?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get partner info\n * GET /v1/partner/info\n */\nexport interface getPartnerInfoOperation {\n readonly path: \"/v1/partner/info\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: Partner;\n success?: boolean;\n};\n}\n\n/**\n * List intent categories\n * GET /v1/partner/intent/categories\n */\nexport interface listIntentCategoriesOperation {\n readonly path: \"/v1/partner/intent/categories\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n count?: number;\n data?: Array<{\n description?: string;\n id?: string;\n /** Minimum intent score to match this category */\n min_intent_score?: number;\n name?: string;\n /** CPM multiplier applied when this intent is detected */\n premium_multiplier?: number;\n purchase_stages?: Array<\"READY_TO_BUY\" | \"COMPARING\" | \"CONSIDERING\" | \"BROWSING\">;\n }>;\n object?: string;\n};\n}\n\n/**\n * Get intent category details\n * GET /v1/partner/intent/categories/{categoryId}\n */\nexport interface getIntentCategoryOperation {\n readonly path: \"/v1/partner/intent/categories/{categoryId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n categoryId: string;\n};\n query: Record<string, unknown>;\n response: {\n description?: string;\n iab_segments?: Array<Record<string, unknown>>;\n id?: string;\n min_intent_score?: number;\n name?: string;\n object?: string;\n premium_multiplier?: number;\n purchase_stages?: Array<string>;\n triggers?: Array<string>;\n};\n}\n\n/**\n * Get intent-based pricing for a screen\n * GET /v1/partner/intent/pricing/{screenId}\n */\nexport interface getIntentPricingOperation {\n readonly path: \"/v1/partner/intent/pricing/{screenId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n category_match?: {\n category_id?: string;\n category_name?: string;\n intent_score?: number;\n premium_multiplier?: number;\n purchase_stage?: string;\n } | null;\n current_intent?: {\n categories?: Array<string>;\n purchase_stage?: \"NONE\" | \"BROWSING\" | \"CONSIDERING\" | \"COMPARING\" | \"READY_TO_BUY\";\n /** Intent confidence score (0-1) */\n score?: number;\n /** Whether an ad swap should be triggered */\n should_trigger?: boolean;\n };\n object?: string;\n pricing?: {\n base_cpm?: number;\n category_id?: string;\n category_name?: string;\n /** Effective CPM after intent premium */\n intent_cpm?: number;\n /** Additional CPM from intent detection */\n intent_premium?: number;\n premium_multiplier?: number;\n };\n screen_id?: string;\n};\n}\n\n/**\n * Trillboards measurement methodology disclosure\n * GET /v1/partner/measurement-methodology\n */\nexport interface getMeasurementMethodologyOperation {\n readonly path: \"/v1/partner/measurement-methodology\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n format?: \"json\";\n};\n response: MeasurementMethodology;\n}\n\n/**\n * Measurement methodology disclosure (JSON, no negotiation)\n * GET /v1/partner/measurement-methodology.json\n */\nexport interface getMeasurementMethodologyJsonOperation {\n readonly path: \"/v1/partner/measurement-methodology.json\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: MeasurementMethodology;\n}\n\n/**\n * Get network configuration\n * GET /v1/partner/network-config\n */\nexport interface getNetworkConfigOperation {\n readonly path: \"/v1/partner/network-config\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n network_config?: PartnerNetworkConfig;\n object?: string;\n parent_partner_id?: string | null;\n};\n}\n\n/**\n * Partial update network configuration\n * PATCH /v1/partner/network-config\n */\nexport interface patchNetworkConfigOperation {\n readonly path: \"/v1/partner/network-config\";\n readonly method: \"PATCH\";\n request: PartnerNetworkConfigUpdate;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n network_config?: PartnerNetworkConfig;\n object?: string;\n parent_partner_id?: string | null;\n};\n}\n\n/**\n * Update network configuration\n * PUT /v1/partner/network-config\n */\nexport interface updateNetworkConfigOperation {\n readonly path: \"/v1/partner/network-config\";\n readonly method: \"PUT\";\n request: PartnerNetworkConfigUpdate;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n network_config?: PartnerNetworkConfig;\n object?: string;\n parent_partner_id?: string | null;\n};\n}\n\n/**\n * Network-wide audience and inventory trends\n * GET /v1/partner/network/trends\n */\nexport interface getNetworkTrendsOperation {\n readonly path: \"/v1/partner/network/trends\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n days?: number;\n};\n response: {\n audience_shift?: Array<Record<string, unknown>>;\n days?: number;\n venue_type_mix?: Array<Record<string, unknown>>;\n};\n}\n\n/**\n * Unauthenticated liveness probe\n * GET /v1/partner/ping\n */\nexport interface partnerPingOperation {\n readonly path: \"/v1/partner/ping\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n ok?: boolean;\n service?: string;\n};\n}\n\n/**\n * Authenticated liveness probe\n * GET /v1/partner/ping/auth\n */\nexport interface partnerPingAuthOperation {\n readonly path: \"/v1/partner/ping/auth\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n ok?: boolean;\n partner_id?: string;\n tier?: \"basic\" | \"developer\" | \"enterprise\";\n};\n}\n\n/**\n * Record a log-level play\n * POST /v1/partner/plays\n */\nexport interface recordPartnerPlayOperation {\n readonly path: \"/v1/partner/plays\";\n readonly method: \"POST\";\n request: PartnerPlay;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n /** The content hash the play named. Absent when the play sent no creative_ref. */\n content_sha256?: string;\n object?: string;\n play_id?: string;\n status?: \"accepted\" | \"duplicate\";\n};\n}\n\n/**\n * Record a batch of log-level plays\n * POST /v1/partner/plays/batch\n */\nexport interface recordPartnerPlaysBatchOperation {\n readonly path: \"/v1/partner/plays/batch\";\n readonly method: \"POST\";\n request: {\n plays: Array<PartnerPlay>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n accepted?: number;\n duplicate?: number;\n errors?: Array<{\n play_id?: string | null;\n reason?: string;\n }>;\n object?: string;\n rejected?: number;\n total?: number;\n};\n}\n\n/**\n * Get machine-readable pricing\n * GET /v1/partner/pricing\n */\nexport interface getPricingOperation {\n readonly path: \"/v1/partner/pricing\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n agent_register_url?: string;\n billing_setup_url?: string;\n /** Volume discount tiers for prepaid credits */\n committed_use?: Record<string, {\n credits_usd?: number;\n discount_pct?: number;\n price_usd?: number;\n }>;\n currency?: string;\n description?: string;\n /** Free tier limits per product and meter */\n free_tiers?: Record<string, unknown>;\n model?: string;\n object?: string;\n /** Pricing details per product */\n products?: {\n attribution?: {\n description?: string;\n meters?: Record<string, {\n first_campaign_free?: boolean;\n per_unit?: string;\n unit_price_usd?: number;\n }>;\n name?: string;\n };\n data_api?: {\n description?: string;\n name?: string;\n notes?: string;\n tiers?: Array<{\n unit_price_usd?: number;\n /** null means unlimited (final tier) */\n up_to?: number | null;\n }>;\n unit?: string;\n };\n data_marketplace?: {\n description?: string;\n meters?: Record<string, {\n free_quota_per_month?: number;\n per_unit?: string;\n unit_price_usd?: number;\n }>;\n name?: string;\n notes?: string;\n revenue_share?: Record<string, unknown>;\n };\n fein_edge_ai?: {\n description?: string;\n name?: string;\n pricing?: string;\n };\n partner_platform?: {\n description?: string;\n name?: string;\n pricing?: string;\n revenue_share?: {\n distributor?: number;\n earner?: number;\n platform?: number;\n };\n };\n programmatic?: {\n description?: string;\n name?: string;\n pricing?: string;\n };\n proof_of_play?: {\n description?: string;\n name?: string;\n notes?: string;\n tiers?: Array<{\n unit_price_usd?: number;\n up_to?: number | null;\n }>;\n unit?: string;\n };\n };\n request_id?: string;\n};\n}\n\n/**\n * Record programmatic ad event (legacy SDK alias)\n * POST /v1/partner/programmatic-event\n */\nexport interface recordProgrammaticEventLegacyAliasOperation {\n readonly path: \"/v1/partner/programmatic-event\";\n readonly method: \"POST\";\n request: {\n ad_id?: string;\n /** Device fingerprint or external device ID */\n device_id?: string;\n /** Short legacy device identifier alias */\n did?: string;\n error_code?: string;\n error_message?: string;\n event: \"ad_started\" | \"ad_ended\" | \"no_fill\" | \"error\";\n reason?: string;\n request_id?: string;\n type?: \"ad_started\" | \"ad_ended\" | \"no_fill\" | \"error\";\n variant_name?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Quick start — register partner and first device in one call\n * POST /v1/partner/quick-start\n */\nexport interface quickStartOperation {\n readonly path: \"/v1/partner/quick-start\";\n readonly method: \"POST\";\n request: {\n /** Company or partner name */\n company_name: string;\n /** Display configuration for the first device */\n device_display?: Record<string, unknown>;\n /** Location for the first device */\n device_location?: Record<string, unknown>;\n /** Name for the first device */\n device_name?: string;\n email: string;\n /** Optional legacy classification. Omit for the default self-serve Partner API key. */\n partner_type?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n credentials?: {\n /** Store securely — shown only once! */\n api_key?: string;\n api_key_prefix?: string;\n warning?: string;\n };\n device?: {\n device_id?: string;\n embed_url?: string;\n external_device_id?: string;\n fingerprint?: string;\n name?: string;\n screen_id?: string;\n status?: string;\n };\n integration?: {\n api_base?: string;\n docs_url?: string;\n embed_url?: string;\n iframe_snippet?: string;\n sdk_snippet?: string;\n sdk_url?: string;\n };\n next_steps?: Array<string>;\n partner?: {\n name?: string;\n partner_id?: string;\n revenue_share_percent?: number;\n slug?: string;\n status?: string;\n };\n sdk_config?: {\n ad_interval?: number;\n allowed_ad_types?: Array<string>;\n cache_size?: number;\n image_duration?: number;\n max_ads_per_hour?: number;\n sound_enabled?: boolean;\n };\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Register a new partner\n * POST /v1/partner/register\n */\nexport interface registerPartnerOperation {\n readonly path: \"/v1/partner/register\";\n readonly method: \"POST\";\n request: {\n /** Whitelist of allowed referrer domains */\n allowed_domains?: Array<string>;\n contact_email: string;\n contact_name?: string;\n /** Company or partner name */\n name: string;\n /** Optional legacy classification. Omit this field — it does not change which API key you receive. Existing integrations may continue sending historical values (including `ctv_publisher`) for backward compatibility; every partner_type mints the same self-serve Partner API key. */\n partner_type?: \"vending_machine\" | \"kiosk\" | \"digital_signage\" | \"retail_display\" | \"ctv_publisher\" | \"other\";\n /** Partner's revenue share percentage (negotiated per partner) */\n revenue_share_percent?: number;\n /** URL-safe identifier (lowercase, no spaces) */\n slug: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Store this securely - only shown once. This ONE self-serve Partner API key authenticates the general Partner API surface in this spec AND the CTV Measurement SDK ingestion surface (`/v2/sdk/*`, `/v2/csi`) — it carries `sdk:*` and `mcp:tools` by default, so no separate CTV or MCP key is issued. Reuse it during OpenRTB onboarding to add buyer capabilities. */\n api_key?: string;\n partner_id?: string;\n /** Optional portal onboarding details for the partner owner account */\n portal_access?: {\n claim_complete_endpoint?: string;\n claim_request_endpoint?: string;\n claim_required?: boolean;\n claim_verify_endpoint?: string;\n email?: string;\n message?: string;\n new_account?: boolean;\n portal_auth_url?: string;\n portal_url?: string;\n user_id?: string;\n };\n revenue_share_percent?: number;\n slug?: string;\n status?: \"pending\" | \"active\";\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Generate a test event\n * POST /v1/partner/sandbox/event\n */\nexport interface generateSandboxEventOperation {\n readonly path: \"/v1/partner/sandbox/event\";\n readonly method: \"POST\";\n request: {\n /** Event type to generate */\n event_type: string;\n /** Target sandbox screen (auto-selects first if omitted) */\n screen_id?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n /** Event payload (varies by event type) */\n data?: Record<string, unknown>;\n event?: string;\n event_id?: string;\n is_sandbox?: boolean;\n object?: string;\n screen_id?: string | null;\n /** Number of SSE connections that received this event */\n sse_delivered?: number;\n timestamp?: string;\n};\n}\n\n/**\n * Reset sandbox data\n * POST /v1/partner/sandbox/reset\n */\nexport interface resetSandboxOperation {\n readonly path: \"/v1/partner/sandbox/reset\";\n readonly method: \"POST\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n events_cleared?: number;\n object?: string;\n reset_at?: string;\n screens_cleared?: number;\n success?: boolean;\n};\n}\n\n/**\n * Create a sandbox screen\n * POST /v1/partner/sandbox/screen\n */\nexport interface createSandboxScreenOperation {\n readonly path: \"/v1/partner/sandbox/screen\";\n readonly method: \"POST\";\n request: {\n /** Custom screen name */\n name?: string;\n /** Override venue type */\n venue_type?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n audience?: {\n avg_dwell_time_sec?: number;\n current_viewers?: number;\n demographics?: Record<string, unknown>;\n };\n created_at?: string;\n device_id?: string;\n display_settings?: Record<string, unknown>;\n fingerprint?: string;\n id?: string;\n is_sandbox?: boolean;\n last_heartbeat?: string;\n location?: {\n city?: string;\n latitude?: number;\n longitude?: number;\n name?: string;\n state?: string;\n venue_type?: string;\n };\n name?: string;\n object?: string;\n partner_id?: string;\n screen_id?: string;\n status?: string;\n};\n}\n\n/**\n * Get sandbox status\n * GET /v1/partner/sandbox/status\n */\nexport interface getSandboxStatusOperation {\n readonly path: \"/v1/partner/sandbox/status\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n last_activity?: string | null;\n object?: string;\n recent_events?: Array<{\n event?: string;\n event_id?: string;\n timestamp?: string;\n }>;\n sandbox_screens?: Array<{\n created_at?: string;\n id?: string;\n last_heartbeat?: string;\n location?: Record<string, unknown>;\n name?: string;\n status?: string;\n }>;\n total_screens?: number;\n total_test_events?: number;\n};\n}\n\n/**\n * List screen groups\n * GET /v1/partner/screen-groups\n */\nexport interface listScreenGroupsOperation {\n readonly path: \"/v1/partner/screen-groups\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n offset?: number;\n};\n response: {\n data?: {\n has_more?: boolean;\n object?: string;\n screen_groups?: Array<ScreenGroup>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Create a screen group\n * POST /v1/partner/screen-groups\n */\nexport interface createScreenGroupOperation {\n readonly path: \"/v1/partner/screen-groups\";\n readonly method: \"POST\";\n request: {\n description?: string;\n name: string;\n screen_ids?: Array<string>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n screen_group?: ScreenGroup;\n };\n success?: boolean;\n};\n}\n\n/**\n * Delete (archive) a screen group\n * DELETE /v1/partner/screen-groups/{groupId}\n */\nexport interface deleteScreenGroupOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n deleted?: boolean;\n id?: string;\n object?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Retrieve a screen group\n * GET /v1/partner/screen-groups/{groupId}\n */\nexport interface getScreenGroupOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n screen_group?: ScreenGroup;\n };\n success?: boolean;\n};\n}\n\n/**\n * Update a screen group\n * POST /v1/partner/screen-groups/{groupId}\n */\nexport interface updateScreenGroupOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}\";\n readonly method: \"POST\";\n request: {\n description?: string;\n name?: string;\n};\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n screen_group?: ScreenGroup;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get analytics for a screen group\n * GET /v1/partner/screen-groups/{groupId}/analytics\n */\nexport interface getScreenGroupAnalyticsOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/analytics\";\n readonly method: \"GET\";\n request: never;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n /** Fleet analytics scoped to the group's live member screens — the same payload as GET /fleet/analytics (which also returns an average attention score alongside the fields below). */\n data?: {\n avg_cpm?: number;\n fill_rate_pct?: number;\n group_id?: string;\n impressions_24h?: number;\n screen_count?: number;\n [key: string]: unknown;\n };\n success?: boolean;\n};\n}\n\n/**\n * Run a fleet command against a screen group\n * POST /v1/partner/screen-groups/{groupId}/commands\n */\nexport interface createScreenGroupCommandOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/commands\";\n readonly method: \"POST\";\n request: {\n command: {\n payload?: Record<string, unknown>;\n type: \"reload\" | \"update_settings\" | \"restart\" | \"screenshot\" | \"clear_cache\" | \"update_content\" | \"refresh_ads\" | \"update_config\" | \"get_state\" | \"show_ad\" | \"play_ad\";\n };\n};\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n fleet_command?: FleetCommand;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get a screen group's content policy\n * GET /v1/partner/screen-groups/{groupId}/content-policy\n */\nexport interface getScreenGroupContentPolicyOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/content-policy\";\n readonly method: \"GET\";\n request: never;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n content_policy?: ScreenGroupContentPolicy;\n };\n success?: boolean;\n};\n}\n\n/**\n * Set a screen group's content policy\n * PUT /v1/partner/screen-groups/{groupId}/content-policy\n */\nexport interface updateScreenGroupContentPolicyOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/content-policy\";\n readonly method: \"PUT\";\n request: ScreenGroupContentPolicyInput;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n content_policy?: ScreenGroupContentPolicy;\n };\n success?: boolean;\n};\n}\n\n/**\n * Remove members from a screen group\n * DELETE /v1/partner/screen-groups/{groupId}/members\n */\nexport interface removeScreenGroupMembersOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/members\";\n readonly method: \"DELETE\";\n request: {\n screen_ids: Array<string>;\n};\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n removed?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * List a screen group's members\n * GET /v1/partner/screen-groups/{groupId}/members\n */\nexport interface listScreenGroupMembersOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/members\";\n readonly method: \"GET\";\n request: never;\n params: {\n groupId: string;\n};\n query: {\n limit?: number;\n offset?: number;\n};\n response: {\n data?: {\n data?: Array<{\n id?: string;\n object?: string;\n }>;\n has_more?: boolean;\n object?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Add members to a screen group\n * POST /v1/partner/screen-groups/{groupId}/members\n */\nexport interface addScreenGroupMembersOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/members\";\n readonly method: \"POST\";\n request: {\n screen_ids: Array<string>;\n};\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n added?: number;\n rejected?: Array<{\n id?: string;\n reason?: string;\n }>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get a screen group's sensing config\n * GET /v1/partner/screen-groups/{groupId}/sensing-config\n */\nexport interface getScreenGroupSensingConfigOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/sensing-config\";\n readonly method: \"GET\";\n request: never;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n sensing_config?: ScreenGroupSensingConfig;\n };\n success?: boolean;\n};\n}\n\n/**\n * Set a screen group's sensing config\n * PUT /v1/partner/screen-groups/{groupId}/sensing-config\n */\nexport interface updateScreenGroupSensingConfigOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/sensing-config\";\n readonly method: \"PUT\";\n request: ScreenGroupSensingConfigInput;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n sensing_config?: ScreenGroupSensingConfig;\n };\n success?: boolean;\n};\n}\n\n/**\n * OpenRTB SDA payload for a screen\n * GET /v1/partner/screen/{external_code}/audience-segments\n */\nexport interface getScreenAudienceSegmentsOperation {\n readonly path: \"/v1/partner/screen/{external_code}/audience-segments\";\n readonly method: \"GET\";\n request: never;\n params: {\n external_code: string;\n};\n query: Record<string, unknown>;\n response: {\n external_code?: string;\n segtax_4?: Array<{\n confidence?: number;\n id?: string;\n }>;\n segtax_600?: Array<{\n confidence?: number;\n id?: string;\n }>;\n};\n}\n\n/**\n * Batch update content preferences\n * POST /v1/partner/screens/batch/content-preferences\n */\nexport interface batchUpdateContentPreferencesOperation {\n readonly path: \"/v1/partner/screens/batch/content-preferences\";\n readonly method: \"POST\";\n request: {\n preferences?: ContentPreferencesInput;\n /** Preset ID to apply (alternative to preferences) */\n preset_id?: string;\n /** Array of screen IDs to update */\n screen_ids: Array<string>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: BatchResult;\n}\n\n/**\n * Batch update display settings\n * POST /v1/partner/screens/batch/display-settings\n */\nexport interface batchUpdateDisplaySettingsOperation {\n readonly path: \"/v1/partner/screens/batch/display-settings\";\n readonly method: \"POST\";\n request: {\n screen_ids: Array<string>;\n settings: DisplaySettingsInput;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: BatchResult;\n}\n\n/**\n * Batch update programmatic settings\n * POST /v1/partner/screens/batch/programmatic-settings\n */\nexport interface batchUpdateProgrammaticSettingsOperation {\n readonly path: \"/v1/partner/screens/batch/programmatic-settings\";\n readonly method: \"POST\";\n request: {\n screen_ids: Array<string>;\n settings: ProgrammaticSettingsInput;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: BatchResult;\n}\n\n/**\n * Resolve screen ID by device identifier\n * GET /v1/partner/screens/resolve\n */\nexport interface resolvePartnerScreenOperation {\n readonly path: \"/v1/partner/screens/resolve\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n device_id?: string;\n external_device_id?: string;\n fingerprint?: string;\n};\n response: {\n data?: {\n device_id?: string;\n external_device_id?: string;\n fingerprint?: string;\n screen_id?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get per-screen analytics\n * GET /v1/partner/screens/{screenId}/analytics\n */\nexport interface getScreenAnalyticsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n period?: \"7d\" | \"30d\" | \"90d\";\n};\n response: {\n data?: ScreenAnalyticsResponse;\n success?: boolean;\n};\n}\n\n/**\n * Current atmosphere (mood, noise, crowd density)\n * GET /v1/partner/screens/{screenId}/analytics/atmosphere\n */\nexport interface getPartnerScreenAtmosphereOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics/atmosphere\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n crowdDensity?: string | null;\n currentViewers?: number | null;\n dataAge?: string;\n engagement?: number | null;\n lastUpdated?: string | null;\n mood?: string | null;\n noiseLevel?: string | null;\n };\n status?: boolean;\n};\n}\n\n/**\n * Time-bucketed venue intelligence with Lincoln-Petersen dedup\n * GET /v1/partner/screens/{screenId}/analytics/intelligence\n */\nexport interface getPartnerScreenIntelligenceOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics/intelligence\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n from: string;\n interval?: \"5min\" | \"15min\" | \"1h\" | \"1d\";\n to: string;\n};\n response: {\n data?: {\n dedup?: {\n camera_count?: number;\n method?: string;\n raw_total?: number;\n unique_total?: number;\n };\n from?: string;\n interval?: string;\n screen_id?: string;\n summary?: {\n avg_attention?: number;\n buckets?: number;\n total_unique_viewers?: number;\n };\n timeseries?: Array<Record<string, unknown>>;\n to?: string;\n };\n status?: boolean;\n};\n}\n\n/**\n * Per-screen taste profile + top-5 creative categories\n * GET /v1/partner/screens/{screenId}/analytics/taste-profile\n */\nexport interface getPartnerScreenTasteProfileOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics/taste-profile\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n computedAt?: string | null;\n daypartDistribution?: Record<string, unknown> | null;\n ready?: boolean;\n sampleCount?: number;\n screenId?: string;\n tasteVersionHash?: string | null;\n topCategories?: Array<Record<string, unknown>>;\n updatedAt?: string | null;\n venueType?: string | null;\n windowEnd?: string | null;\n windowStart?: string | null;\n };\n status?: boolean;\n};\n}\n\n/**\n * Hourly foot-traffic patterns for a screen\n * GET /v1/partner/screens/{screenId}/analytics/traffic\n */\nexport interface getPartnerScreenTrafficOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics/traffic\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n days?: number;\n};\n response: {\n data?: {\n busyScore?: number;\n hourlyTraffic?: Array<Record<string, unknown>>;\n periodDays?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Verified Attention Seconds + CPM tier for a screen\n * GET /v1/partner/screens/{screenId}/analytics/vas\n */\nexport interface getPartnerScreenVASOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics/vas\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n avgAttentionScore?: number;\n avgFaceCount?: number;\n avgVasWeighted?: number;\n lastMeasurement?: string | null;\n medianVas?: number;\n multiplier?: number;\n p90Vas?: number;\n tier?: string;\n totalMeasurements?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Apply content preset to screen\n * POST /v1/partner/screens/{screenId}/apply-preset\n */\nexport interface applyContentPresetOperation {\n readonly path: \"/v1/partner/screens/{screenId}/apply-preset\";\n readonly method: \"POST\";\n request: {\n /** ID of the preset to apply */\n preset_id: string;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ContentPreferencesResponse & {\n preset_applied?: string;\n };\n}\n\n/**\n * Get real-time audience metrics\n * GET /v1/partner/screens/{screenId}/audience-metrics\n */\nexport interface getAudienceMetricsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/audience-metrics\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n include_history?: boolean;\n period?: \"1h\" | \"6h\" | \"24h\";\n};\n response: AudienceMetricsResponse;\n}\n\n/**\n * Get screen content preferences\n * GET /v1/partner/screens/{screenId}/content-preferences\n */\nexport interface getContentPreferencesOperation {\n readonly path: \"/v1/partner/screens/{screenId}/content-preferences\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ContentPreferencesResponse;\n}\n\n/**\n * Update content preferences (partial)\n * PATCH /v1/partner/screens/{screenId}/content-preferences\n */\nexport interface patchContentPreferencesOperation {\n readonly path: \"/v1/partner/screens/{screenId}/content-preferences\";\n readonly method: \"PATCH\";\n request: ContentPreferencesInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ContentPreferencesResponse;\n}\n\n/**\n * Update content preferences (full)\n * PUT /v1/partner/screens/{screenId}/content-preferences\n */\nexport interface updateContentPreferencesOperation {\n readonly path: \"/v1/partner/screens/{screenId}/content-preferences\";\n readonly method: \"PUT\";\n request: ContentPreferencesInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ContentPreferencesResponse;\n}\n\n/**\n * List default streams for a screen\n * GET /v1/partner/screens/{screenId}/default-streams\n */\nexport interface listPartnerDefaultStreamsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n current?: PartnerDefaultStream;\n streams?: Array<PartnerDefaultStream>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Create a default stream (URL-based)\n * POST /v1/partner/screens/{screenId}/default-streams\n */\nexport interface createPartnerDefaultStreamOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams\";\n readonly method: \"POST\";\n request: PartnerDefaultStreamCreate;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerDefaultStream;\n success?: boolean;\n};\n}\n\n/**\n * Upload image carousel default stream\n * POST /v1/partner/screens/{screenId}/default-streams/images\n */\nexport interface createPartnerDefaultStreamImagesOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams/images\";\n readonly method: \"POST\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerDefaultStream;\n success?: boolean;\n};\n}\n\n/**\n * Reorder default streams\n * PUT /v1/partner/screens/{screenId}/default-streams/order\n */\nexport interface reorderPartnerDefaultStreamsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams/order\";\n readonly method: \"PUT\";\n request: {\n order: Array<string>;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Upload a video default stream\n * POST /v1/partner/screens/{screenId}/default-streams/upload\n */\nexport interface createPartnerDefaultStreamUploadOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams/upload\";\n readonly method: \"POST\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerDefaultStream;\n success?: boolean;\n};\n}\n\n/**\n * Delete a default stream\n * DELETE /v1/partner/screens/{screenId}/default-streams/{streamId}\n */\nexport interface deletePartnerDefaultStreamOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams/{streamId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n screenId: string;\n streamId: string;\n};\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Update a default stream\n * PUT /v1/partner/screens/{screenId}/default-streams/{streamId}\n */\nexport interface updatePartnerDefaultStreamOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams/{streamId}\";\n readonly method: \"PUT\";\n request: PartnerDefaultStreamUpdate;\n params: {\n screenId: string;\n streamId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerDefaultStream;\n success?: boolean;\n};\n}\n\n/**\n * Get screen demographics\n * GET /v1/partner/screens/{screenId}/demographics\n */\nexport interface getScreenDemographicsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/demographics\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n period?: \"24h\" | \"7d\" | \"30d\";\n};\n response: DemographicsResponse;\n}\n\n/**\n * Get display settings\n * GET /v1/partner/screens/{screenId}/display-settings\n */\nexport interface getDisplaySettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/display-settings\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: DisplaySettingsResponse;\n}\n\n/**\n * Update display settings (partial)\n * PATCH /v1/partner/screens/{screenId}/display-settings\n */\nexport interface patchDisplaySettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/display-settings\";\n readonly method: \"PATCH\";\n request: DisplaySettingsInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: DisplaySettingsResponse;\n}\n\n/**\n * Update display settings (full)\n * PUT /v1/partner/screens/{screenId}/display-settings\n */\nexport interface updateDisplaySettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/display-settings\";\n readonly method: \"PUT\";\n request: DisplaySettingsInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: DisplaySettingsResponse;\n}\n\n/**\n * Get L-Bar settings\n * GET /v1/partner/screens/{screenId}/l-bar\n */\nexport interface getPartnerLBarSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/l-bar\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerLBarSettings;\n success?: boolean;\n};\n}\n\n/**\n * Update L-Bar settings\n * PUT /v1/partner/screens/{screenId}/l-bar\n */\nexport interface updatePartnerLBarSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/l-bar\";\n readonly method: \"PUT\";\n request: {\n display_preferences?: PartnerLBarDisplayPreferences;\n l_bar_content?: PartnerLBarContent;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerLBarSettings;\n success?: boolean;\n};\n}\n\n/**\n * Update L-Bar live updates\n * PUT /v1/partner/screens/{screenId}/l-bar/live-updates\n */\nexport interface updatePartnerLBarLiveUpdatesOperation {\n readonly path: \"/v1/partner/screens/{screenId}/l-bar/live-updates\";\n readonly method: \"PUT\";\n request: {\n custom_status?: string;\n promotion?: string;\n queue_number?: string;\n wait_time_minutes?: number;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Get programmatic settings\n * GET /v1/partner/screens/{screenId}/programmatic-settings\n */\nexport interface getProgrammaticSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/programmatic-settings\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ProgrammaticSettingsResponse;\n}\n\n/**\n * Update programmatic settings (partial)\n * PATCH /v1/partner/screens/{screenId}/programmatic-settings\n */\nexport interface patchProgrammaticSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/programmatic-settings\";\n readonly method: \"PATCH\";\n request: ProgrammaticSettingsInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ProgrammaticSettingsResponse;\n}\n\n/**\n * Update programmatic settings (full)\n * PUT /v1/partner/screens/{screenId}/programmatic-settings\n */\nexport interface updateProgrammaticSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/programmatic-settings\";\n readonly method: \"PUT\";\n request: ProgrammaticSettingsInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ProgrammaticSettingsResponse;\n}\n\n/**\n * Resolve the effective content policy for a screen\n * GET /v1/partner/screens/{screenId}/resolved-content-policy\n */\nexport interface getResolvedContentPolicyOperation {\n readonly path: \"/v1/partner/screens/{screenId}/resolved-content-policy\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: ResolvedContentPolicy;\n success?: boolean;\n};\n}\n\n/**\n * Resolve the effective sensing config for a screen\n * GET /v1/partner/screens/{screenId}/resolved-sensing-config\n */\nexport interface getResolvedSensingConfigOperation {\n readonly path: \"/v1/partner/screens/{screenId}/resolved-sensing-config\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: ResolvedSensingConfig;\n success?: boolean;\n};\n}\n\n/**\n * Read the custom sensing chips a screen is producing\n * GET /v1/partner/screens/{screenId}/sensing/chips\n */\nexport interface getScreenSensingChipsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/sensing/chips\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n days?: number;\n limit?: number;\n offset?: number;\n};\n response: {\n data?: {\n /** The custom field_keys this screen senses (from its active profile) */\n fields?: Array<{\n description?: string;\n field_key?: string;\n type?: string;\n }>;\n /** Recent chip observations (one row per field_key per observation) */\n rows?: Array<Record<string, unknown>>;\n screen_id?: string;\n total?: number;\n truncated?: boolean;\n };\n success?: boolean;\n};\n}\n\n/**\n * Generate a custom sensing profile from a natural-language intent\n * POST /v1/partner/screens/{screenId}/sensing/interpret-intent\n */\nexport interface interpretScreenSensingIntentOperation {\n readonly path: \"/v1/partner/screens/{screenId}/sensing/interpret-intent\";\n readonly method: \"POST\";\n request: {\n /** Optional device-capability hint for model selection */\n deviceTier?: string;\n /** Natural-language description of what to sense */\n intent: string;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n /** The generated profile — pass to /sensing/profile to deploy */\n profile?: Record<string, unknown>;\n screen_id?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Deploy a custom sensing profile to a screen\n * POST /v1/partner/screens/{screenId}/sensing/profile\n */\nexport interface deployScreenSensingProfileOperation {\n readonly path: \"/v1/partner/screens/{screenId}/sensing/profile\";\n readonly method: \"POST\";\n request: {\n /** The sensing profile object returned by interpret-intent */\n profile: Record<string, unknown>;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n command?: Record<string, unknown>;\n profile?: Record<string, unknown>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Read a screen's AI sensing performance settings\n * GET /v1/partner/screens/{screenId}/sensing/settings\n */\nexport interface getScreenSensingSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/sensing/settings\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: SensingSettingsResponse;\n success?: boolean;\n};\n}\n\n/**\n * Update a screen's AI sensing performance mode\n * PATCH /v1/partner/screens/{screenId}/sensing/settings\n */\nexport interface updateScreenSensingSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/sensing/settings\";\n readonly method: \"PATCH\";\n request: {\n performance_mode: SensingPerformanceMode;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n command?: Record<string, unknown>;\n profile?: Record<string, unknown>;\n settings?: SensingSettingsResponse;\n };\n success?: boolean;\n};\n}\n\n/**\n * List your Sense Anything Routes\n * GET /v1/partner/sensing/routes\n */\nexport interface listPartnerSensingRoutesOperation {\n readonly path: \"/v1/partner/sensing/routes\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n includeDisabled?: boolean;\n};\n response: {\n data?: {\n routes?: Array<SensingRoute>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Create a Sense Anything Route (threshold or scheduled-digest alert)\n * POST /v1/partner/sensing/routes\n */\nexport interface createPartnerSensingRouteOperation {\n readonly path: \"/v1/partner/sensing/routes\";\n readonly method: \"POST\";\n request: CreatePartnerSensingRouteRequest;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n route?: SensingRoute;\n };\n success?: boolean;\n};\n}\n\n/**\n * Delete (soft) a Sense Anything Route\n * DELETE /v1/partner/sensing/routes/{routeId}\n */\nexport interface deletePartnerSensingRouteOperation {\n readonly path: \"/v1/partner/sensing/routes/{routeId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n routeId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n routeId?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get one Sense Anything Route\n * GET /v1/partner/sensing/routes/{routeId}\n */\nexport interface getPartnerSensingRouteOperation {\n readonly path: \"/v1/partner/sensing/routes/{routeId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n routeId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n route?: SensingRoute;\n };\n success?: boolean;\n};\n}\n\n/**\n * Update a Sense Anything Route (partial, non-clobbering)\n * PATCH /v1/partner/sensing/routes/{routeId}\n */\nexport interface updatePartnerSensingRouteOperation {\n readonly path: \"/v1/partner/sensing/routes/{routeId}\";\n readonly method: \"PATCH\";\n request: UpdatePartnerSensingRouteRequest;\n params: {\n routeId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n route?: SensingRoute;\n };\n success?: boolean;\n};\n}\n\n/**\n * Create Stripe Connect account\n * POST /v1/partner/stripe/connect\n */\nexport interface createStripeConnectAccountOperation {\n readonly path: \"/v1/partner/stripe/connect\";\n readonly method: \"POST\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n account_id?: string;\n charges_enabled?: boolean;\n country?: string;\n default_currency?: string;\n details_submitted?: boolean;\n next_step?: string | null;\n payouts_enabled?: boolean;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get Stripe dashboard login link\n * GET /v1/partner/stripe/login-link\n */\nexport interface getStripeLoginLinkOperation {\n readonly path: \"/v1/partner/stripe/login-link\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n account_id?: string;\n instructions?: string;\n login_url?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get Stripe onboarding link\n * GET /v1/partner/stripe/onboarding-link\n */\nexport interface getStripeOnboardingLinkOperation {\n readonly path: \"/v1/partner/stripe/onboarding-link\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n account_id?: string;\n expires_at?: string;\n instructions?: string;\n onboarding_url?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Request a payout\n * POST /v1/partner/stripe/payout\n */\nexport interface requestStripePayoutOperation {\n readonly path: \"/v1/partner/stripe/payout\";\n readonly method: \"POST\";\n request: {\n /** Payout amount in cents (minimum $5.00) */\n amount_cents: number;\n /** Optional description for the payout */\n description?: string;\n /** Deduplication key to prevent duplicate payouts */\n idempotency_key?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n amount_cents?: number;\n created?: string;\n currency?: string;\n description?: string;\n destination?: string;\n /** Estimated arrival date (2-3 business days) */\n estimated_arrival?: string;\n status?: \"pending\" | \"reversed\";\n transfer_id?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get payout history\n * GET /v1/partner/stripe/payouts\n */\nexport interface getStripePayoutHistoryOperation {\n readonly path: \"/v1/partner/stripe/payouts\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n};\n response: {\n data?: {\n has_more?: boolean;\n total?: number;\n transfers?: Array<{\n amount_cents?: number;\n created?: string;\n currency?: string;\n description?: string;\n status?: \"completed\" | \"reversed\";\n transfer_id?: string;\n }>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get Stripe account status\n * GET /v1/partner/stripe/status\n */\nexport interface getStripeAccountStatusOperation {\n readonly path: \"/v1/partner/stripe/status\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n account_id?: string | null;\n charges_enabled?: boolean;\n country?: string;\n default_currency?: string;\n details_submitted?: boolean;\n has_account?: boolean;\n payouts_enabled?: boolean;\n requirements?: {\n currently_due?: Array<string>;\n disabled_reason?: string | null;\n eventually_due?: Array<string>;\n past_due?: Array<string>;\n };\n status?: \"not_connected\" | \"pending\" | \"active\" | \"restricted\";\n };\n success?: boolean;\n};\n}\n\n/**\n * Stripe webhook receiver\n * POST /v1/partner/stripe/webhook\n */\nexport interface handleStripeWebhookOperation {\n readonly path: \"/v1/partner/stripe/webhook\";\n readonly method: \"POST\";\n request: Record<string, unknown>;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n event_type?: string;\n received?: boolean;\n};\n}\n\n/**\n * List team members\n * GET /v1/partner/team\n */\nexport interface listTeamMembersOperation {\n readonly path: \"/v1/partner/team\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n status?: \"active\" | \"pending\" | \"all\";\n};\n response: {\n data?: {\n members?: Array<TeamMember>;\n partner?: {\n name?: string;\n slug?: string;\n };\n total?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Accept team invitation\n * POST /v1/partner/team/accept\n */\nexport interface acceptInvitationOperation {\n readonly path: \"/v1/partner/team/accept\";\n readonly method: \"POST\";\n request: {\n /** Invitation token from email link */\n token: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n partner?: {\n id?: string;\n name?: string;\n slug?: string;\n };\n permissions?: RolePermissions;\n portal_url?: string;\n role?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Invite a team member\n * POST /v1/partner/team/invite\n */\nexport interface inviteTeamMemberOperation {\n readonly path: \"/v1/partner/team/invite\";\n readonly method: \"POST\";\n request: {\n /** Email address of the invitee */\n email: string;\n /** Optional name of the invitee */\n name?: string;\n /** Role to assign to the team member */\n role?: \"admin\" | \"viewer\";\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: TeamInviteResponse;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get invitation details\n * GET /v1/partner/team/invite/{token}\n */\nexport interface getInvitationByTokenOperation {\n readonly path: \"/v1/partner/team/invite/{token}\";\n readonly method: \"GET\";\n request: never;\n params: {\n token: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n email?: string;\n expires_at?: string;\n invited_at?: string;\n partner?: {\n name?: string;\n slug?: string;\n };\n role?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Resend invitation\n * POST /v1/partner/team/{membershipId}/resend\n */\nexport interface resendInvitationOperation {\n readonly path: \"/v1/partner/team/{membershipId}/resend\";\n readonly method: \"POST\";\n request: never;\n params: {\n membershipId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n email?: string;\n expires_at?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Remove a team member\n * DELETE /v1/partner/team/{userId}\n */\nexport interface removeTeamMemberOperation {\n readonly path: \"/v1/partner/team/{userId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n userId: string;\n};\n query: Record<string, unknown>;\n response: {\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Change team member role\n * PATCH /v1/partner/team/{userId}/role\n */\nexport interface changeTeamMemberRoleOperation {\n readonly path: \"/v1/partner/team/{userId}/role\";\n readonly method: \"PATCH\";\n request: {\n role: \"admin\" | \"viewer\";\n};\n params: {\n userId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n new_role?: string;\n old_role?: string;\n permissions?: RolePermissions;\n user_id?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get public key for proof verification\n * GET /v1/partner/tracking/.well-known/public-key\n */\nexport interface getTrackingPublicKeyOperation {\n readonly path: \"/v1/partner/tracking/.well-known/public-key\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Signature algorithm (ed25519 for public key, sha256 for HMAC fallback) */\n algorithm?: \"ed25519\" | \"sha256\";\n documentation?: string;\n /** Key identifier for rotation support */\n keyId?: string;\n /** Hex-encoded Ed25519 public key (null if using HMAC fallback) */\n publicKey?: string | null;\n verificationUrl?: string;\n version?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Record batch impressions with proof generation\n * POST /v1/partner/tracking/batch\n */\nexport interface recordTrackingBatchOperation {\n readonly path: \"/v1/partner/tracking/batch\";\n readonly method: \"POST\";\n request: {\n impressions: Array<{\n /** Ad identifier from VAST response */\n ad_id: string;\n /** Optional audience metrics */\n audience?: {\n /** Attention score (0-1) */\n attention?: number;\n /** How long viewers were present */\n dwell_time_ms?: number;\n /** Number of viewers detected */\n face_count?: number;\n };\n /** Partner's device identifier */\n device_id: string;\n /** Video duration played in milliseconds */\n duration_ms?: number;\n /** VAST event type */\n event?: \"start\" | \"complete\" | \"error\" | \"firstQuartile\" | \"midpoint\" | \"thirdQuartile\";\n /** Optional client-generated ID (auto-generated if not provided) */\n impression_id?: string;\n /** When the event occurred (defaults to now) */\n timestamp?: string;\n }>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Number of duplicate impressions skipped */\n duplicates?: number;\n /** Details of failed impressions (max 10 shown) */\n errors?: Array<{\n error?: string;\n impression_id?: string;\n }>;\n /** Number of impressions that failed validation */\n failed?: number;\n /** Number of impressions successfully recorded */\n processed?: number;\n /** Time to process the batch */\n processing_time_ms?: number;\n /** Cryptographic proofs for each processed impression */\n proofs?: Array<{\n impression_id?: string;\n public_key_id?: string;\n /** Ed25519 signature (or SHA256 fallback) */\n signature?: string;\n }>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get API usage summary\n * GET /v1/partner/usage\n */\nexport interface getUsageSummaryOperation {\n readonly path: \"/v1/partner/usage\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n end_date?: string;\n start_date?: string;\n};\n response: {\n /** Average response time in milliseconds */\n avgResponseTime?: number;\n dailyBreakdown?: Array<{\n avg_response_ms?: number;\n /** The UTC day, sent as the timestamp of its midnight. */\n date?: string;\n total_calls?: number;\n total_errors?: number;\n }>;\n /** Error rate percentage (0-100) */\n errorRate?: number;\n object?: string;\n period?: {\n end_date?: string;\n start_date?: string;\n };\n topEndpoints?: Array<{\n endpoint?: string;\n method?: string;\n total_calls?: number;\n total_errors?: number;\n }>;\n totalCalls?: number;\n totalErrors?: number;\n};\n}\n\n/**\n * Get real-time usage dashboard\n * GET /v1/partner/usage/dashboard\n */\nexport interface getUsageDashboardOperation {\n readonly path: \"/v1/partner/usage/dashboard\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n last_7_days?: {\n avg_response_ms?: number;\n daily_breakdown?: Array<{\n avg_response_ms?: number;\n /** The UTC day, sent as the timestamp of its midnight. */\n date?: string;\n total_calls?: number;\n total_errors?: number;\n }>;\n error_rate?: number;\n total_calls?: number;\n total_errors?: number;\n };\n object?: string;\n rate_limit?: {\n current_rate?: number;\n max_rate?: number;\n percent_used?: number;\n window_reset?: string;\n window_seconds?: number;\n };\n today?: {\n avg_response_ms?: number;\n error_rate?: number;\n top_endpoints?: Array<{\n avg_response_ms?: number;\n endpoint?: string;\n method?: string;\n total_calls?: number;\n total_errors?: number;\n }>;\n total_calls?: number;\n total_errors?: number;\n };\n};\n}\n\n/**\n * Get rate limit status\n * GET /v1/partner/usage/rate-limit\n */\nexport interface getRateLimitStatusOperation {\n readonly path: \"/v1/partner/usage/rate-limit\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n current_rate?: number;\n max_rate?: number;\n object?: string;\n percent_used?: number;\n tier?: \"basic\" | \"developer\" | \"enterprise\";\n window_reset?: string;\n window_seconds?: number;\n};\n}\n\n/**\n * Get VAST configuration for direct Google integration\n * GET /v1/partner/vast/config\n */\nexport interface getVastConfigOperation {\n readonly path: \"/v1/partner/vast/config\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n device_id?: string;\n screen_id?: string;\n};\n response: {\n data?: {\n /** Google Ad Manager ad unit path */\n ad_unit?: string;\n /** IAB content categories to block */\n blocked_categories?: Array<string>;\n /** How long to cache this config */\n cache_ttl_seconds?: number;\n /** Floor price in cents (CPM) */\n floor_price_cpm?: number;\n integration_guide?: string;\n parameters?: {\n height?: number;\n /** Maximum ad duration in seconds */\n max_duration?: number;\n /** Minimum ad duration in seconds */\n min_duration?: number;\n /** Venue type code for targeting */\n venuetype?: number;\n width?: number;\n };\n /** Recommended VAST fetch timeout */\n timeout_ms?: number;\n tracking?: {\n batch_endpoint?: string;\n max_batch_size?: number;\n single_endpoint?: string;\n };\n /** Configuration variant name for debugging */\n variant?: string;\n /** VAST URL template with placeholders for {width} and {height}. Partner replaces placeholders and appends correlator. */\n vast_tag_template?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * List nearby Trillboards venues\n * GET /v1/partner/venues/nearby\n */\nexport interface getVenuesNearbyOperation {\n readonly path: \"/v1/partner/venues/nearby\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n lat: number;\n lng: number;\n radius_km?: number;\n};\n response: {\n count?: number;\n venues?: Array<{\n distance_m?: number;\n screen_id?: string;\n venue_type?: string;\n }>;\n};\n}\n\n/**\n * Real-world atmosphere signals for a venue\n * GET /v1/partner/venues/{screenId}/atmosphere\n */\nexport interface getVenueAtmosphereOperation {\n readonly path: \"/v1/partner/venues/{screenId}/atmosphere\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n days?: number;\n};\n response: {\n days?: number;\n dominant_ambience?: string;\n lighting_band?: \"dark\" | \"dim\" | \"lit\" | \"bright\";\n motion_intensity?: number;\n noise_level_db?: number;\n screen_id?: string;\n};\n}\n\n/**\n * Foot-traffic profile for a venue\n * GET /v1/partner/venues/{screenId}/traffic\n */\nexport interface getVenueTrafficOperation {\n readonly path: \"/v1/partner/venues/{screenId}/traffic\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n days?: number;\n};\n response: {\n daily?: Array<Record<string, unknown>>;\n hourly?: Array<{\n face_count_avg?: number;\n hour?: number;\n }>;\n screen_id?: string;\n};\n}\n\n/**\n * Get venue intelligence report\n * GET /v1/partner/venues/{venueId}/intelligence\n */\nexport interface getVenueIntelligenceOperation {\n readonly path: \"/v1/partner/venues/{venueId}/intelligence\";\n readonly method: \"GET\";\n request: never;\n params: {\n venueId: string;\n};\n query: {\n from: string;\n interval?: \"5min\" | \"15min\" | \"1h\" | \"1d\";\n to: string;\n};\n response: {\n dedup?: {\n camera_count?: number;\n method?: \"lincoln_petersen\" | \"single_camera\";\n raw_total?: number;\n unique_total?: number;\n };\n from?: string;\n interval?: string;\n summary?: {\n avg_attention?: number;\n buckets?: number;\n total_unique_viewers?: number;\n };\n timeseries?: Array<{\n age_distribution?: Record<string, number>;\n avg_attention?: number;\n avg_engagement?: number;\n avg_noise_level?: number | null;\n brand_mention_windows?: number;\n bucket?: string;\n gender_distribution?: {\n female?: number;\n male?: number;\n };\n income_level?: string | null;\n measurements?: number;\n mood?: string | null;\n peak_viewers?: number;\n raw_viewers?: number;\n screen_count?: number;\n /** Deduplicated viewer count */\n viewers?: number;\n weather?: {\n condition?: string | null;\n is_raining?: boolean;\n temperature_f?: number | null;\n };\n }>;\n to?: string;\n venue_id?: string;\n};\n}\n\n/**\n * List webhooks\n * GET /v1/partner/webhooks\n */\nexport interface listWebhooksOperation {\n readonly path: \"/v1/partner/webhooks\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n webhooks?: Array<Webhook>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Create webhook subscription\n * POST /v1/partner/webhooks\n */\nexport interface createWebhookOperation {\n readonly path: \"/v1/partner/webhooks\";\n readonly method: \"POST\";\n request: {\n /** Events to subscribe to */\n events: Array<\"device.online\" | \"device.offline\" | \"device.requirements_updated\" | \"impression.recorded\" | \"creative.rewards_ready\" | \"campaign.allocated\" | \"payout.processed\" | \"sensing.threshold_crossed\" | \"audience.spike\" | \"audience.venue_busy\" | \"audience.purchase_intent\" | \"audience.demographics_update\" | \"venue.traffic_summary\" | \"programmatic.ad_started\" | \"programmatic.ad_ended\" | \"programmatic.no_fill\" | \"programmatic.error\" | \"screen_group.created\" | \"screen_group.updated\" | \"screen_group.deleted\" | \"screen_group.member.added\" | \"screen_group.member.removed\" | \"screen_group.content_policy.updated\" | \"screen_group.sensing_config.updated\" | \"fleet_command.completed\" | \"fleet_command.failed\">;\n /** Webhook endpoint URL (HTTPS required in production) */\n url: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n events?: Array<string>;\n /** Webhook signing secret (shown only once!) */\n secret?: string;\n status?: \"active\" | \"inactive\";\n url?: string;\n webhook_id?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Delete webhook\n * DELETE /v1/partner/webhooks/{webhookId}\n */\nexport interface deleteWebhookOperation {\n readonly path: \"/v1/partner/webhooks/{webhookId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n webhookId: string;\n};\n query: Record<string, unknown>;\n response: {\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Update webhook\n * PATCH /v1/partner/webhooks/{webhookId}\n */\nexport interface updateWebhookOperation {\n readonly path: \"/v1/partner/webhooks/{webhookId}\";\n readonly method: \"PATCH\";\n request: {\n events?: Array<\"device.online\" | \"device.offline\" | \"device.requirements_updated\" | \"impression.recorded\" | \"creative.rewards_ready\" | \"campaign.allocated\" | \"payout.processed\" | \"sensing.threshold_crossed\" | \"audience.spike\" | \"audience.venue_busy\" | \"audience.purchase_intent\" | \"audience.demographics_update\" | \"venue.traffic_summary\" | \"programmatic.ad_started\" | \"programmatic.ad_ended\" | \"programmatic.no_fill\" | \"programmatic.error\" | \"screen_group.created\" | \"screen_group.updated\" | \"screen_group.deleted\" | \"screen_group.member.added\" | \"screen_group.member.removed\" | \"screen_group.content_policy.updated\" | \"screen_group.sensing_config.updated\" | \"fleet_command.completed\" | \"fleet_command.failed\">;\n status?: \"active\" | \"inactive\";\n url?: string;\n};\n params: {\n webhookId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: Webhook;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get delivery history\n * GET /v1/partner/webhooks/{webhookId}/deliveries\n */\nexport interface getWebhookDeliveriesOperation {\n readonly path: \"/v1/partner/webhooks/{webhookId}/deliveries\";\n readonly method: \"GET\";\n request: never;\n params: {\n webhookId: string;\n};\n query: {\n limit?: number;\n};\n response: {\n data?: {\n deliveries?: Array<WebhookDelivery>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Test webhook\n * POST /v1/partner/webhooks/{webhookId}/test\n */\nexport interface testWebhookOperation {\n readonly path: \"/v1/partner/webhooks/{webhookId}/test\";\n readonly method: \"POST\";\n request: never;\n params: {\n webhookId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n error?: string | null;\n response_time_ms?: number;\n status?: number;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Dry-run validate a request body against an operation schema\n * POST /v1/playground/{operation_id}\n */\nexport interface playgroundDryRunOperation {\n readonly path: \"/v1/playground/{operation_id}\";\n readonly method: \"POST\";\n request: never;\n params: {\n /** The operationId of the endpoint to validate against. */\n operation_id: string;\n};\n query: Record<string, unknown>;\n response: PlaygroundResult;\n}\n\n/**\n * Look up metadata for a recent API request\n * GET /v1/requests/{request_id}\n */\nexport interface getRequestMetadataOperation {\n readonly path: \"/v1/requests/{request_id}\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** Server-generated request ID to look up. */\n request_id: string;\n};\n query: Record<string, unknown>;\n response: RequestMetadataResponse;\n}\n\n\n// ─── Type registry ───────────────────────────────────────────────────────\n\nexport const generatedTypes = {\n schemaCount: 194,\n operationCount: 242,\n schemas: [\"Ad\",\"AdSettings\",\"AdvertisingIdentity\",\"AnalyticsData\",\"AudienceAnalyticsResponse\",\"AudienceAttentionPrediction\",\"AudienceData\",\"AudienceExportResponse\",\"AudienceLoyaltyFeaturesRow\",\"AudienceMetricsResponse\",\"AudienceSegment\",\"AudienceSocialAttention\",\"BatchResult\",\"BillingInfo\",\"BleScanResultData\",\"CanonicalError\",\"ChangelogEntry\",\"ChangelogResponse\",\"CompletedImpressions\",\"ContentFitSettings\",\"ContentPreferencesInput\",\"ContentPreferencesResponse\",\"ContentPreset\",\"CreatePartnerSensingRouteRequest\",\"CreativeClassification\",\"CreativeRewardsReadyEvent\",\"DashboardResponse\",\"DataApi_AdSightingRecord\",\"DataApi_AdSightingsHistoryResponse\",\"DataApi_AdSightingsResponse\",\"DataApi_AggregateResponse\",\"DataApi_Error\",\"DataApi_ErrorCode\",\"DataApi_ExampleHealthcheckResponse\",\"DataApi_FeedbackRewardQuery\",\"DataApi_LookalikeResponse\",\"DataApi_QueryDslBody\",\"DataApi_QueryDslResponse\",\"DataApi_QueryFilter\",\"DataApi_QueryOrderBy\",\"DataApi_ScreenAudienceResponse\",\"DataApi_ScreenHistoryResponse\",\"DataApi_ScreensListResponse\",\"DataApi_SemanticSearchResponse\",\"DataApi_StatsResponse\",\"DataApi_StreamResponse\",\"DataApi_UsageResponse\",\"DataExportSummaryResponse\",\"DemandPartners\",\"DemographicsResponse\",\"Device\",\"DeviceAuthRefusal\",\"DeviceCapabilityPayload\",\"DeviceSummary\",\"DisplaySettingsInput\",\"DisplaySettingsResponse\",\"EarnerScreens\",\"EarningsBreakdownResponse\",\"EarningsData\",\"EarningsResponse\",\"EarningsSources\",\"Error\",\"ErrorCode\",\"ErrorCodeResponse\",\"ErrorEntry\",\"ErrorEnvelope\",\"ErrorListItem\",\"ErrorListResponse\",\"ErrorType\",\"ExtendedIdentity\",\"FeedbackBrand\",\"FeedbackBrandRequest\",\"FeedbackBrandResponse\",\"FeedbackCreateRequest\",\"FeedbackCreativeReview\",\"FeedbackExperiment\",\"FeedbackExperimentDetail\",\"FeedbackExperimentDetailResponse\",\"FeedbackExperimentResponse\",\"FeedbackGenerateRequest\",\"FeedbackGenerateResponse\",\"FeedbackGeneratedCreative\",\"FeedbackImportRequest\",\"FeedbackPolicyRequest\",\"FeedbackRecoveredCreative\",\"FeedbackRegisterVariantsRequest\",\"FeedbackReviewRequest\",\"FeedbackReviewResponse\",\"FeedbackRewardQuery\",\"FeedbackRewardRow\",\"FeedbackRewards\",\"FeedbackRewardsResponse\",\"FeedbackStopRequest\",\"FeedbackVariant\",\"FleetCommand\",\"FleetCommandEvent\",\"HeaderBiddingSettings\",\"HttpProbeResult\",\"HybridError\",\"Impression\",\"LegacyCodedError\",\"LegacyError\",\"Mcp_create_media_buy_Args\",\"Mcp_discover_inventory_Args\",\"Mcp_get_media_buy_delivery_Args\",\"Mcp_get_media_buys_Args\",\"Mcp_get_products_Args\",\"Mcp_list_accounts_Args\",\"Mcp_sync_accounts_Args\",\"Mcp_sync_creatives_Args\",\"Mcp_update_media_buy_Args\",\"MdnsNetworkDevice\",\"MeasurementMethodology\",\"OpenrtbRevenueScreenHourly\",\"Pagination\",\"Partner\",\"PartnerAdDeliveryProfile\",\"PartnerCreativeWarnings\",\"PartnerDefaultStream\",\"PartnerDefaultStreamCreate\",\"PartnerDefaultStreamUpdate\",\"PartnerDeviceBatchRegistrationInput\",\"PartnerDeviceHeartbeatBody\",\"PartnerDevicePlay\",\"PartnerDeviceRegistrationInput\",\"PartnerHeartbeatCommand\",\"PartnerHeartbeatData\",\"PartnerHeartbeatMeasurementMetadata\",\"PartnerHeartbeatQueryData\",\"PartnerHeartbeatQueryResponse\",\"PartnerHeartbeatRequest\",\"PartnerHeartbeatResponse\",\"PartnerHeartbeatTelemetry\",\"PartnerInteraction\",\"PartnerLBarContent\",\"PartnerLBarDisplayPreferences\",\"PartnerLBarSettings\",\"PartnerLocationCapability\",\"PartnerLocationRail\",\"PartnerMonetizationRequirements\",\"PartnerNetworkConfig\",\"PartnerNetworkConfigUpdate\",\"PartnerPlay\",\"PartnerReconciliationReports\",\"PartnerRegistrationDisplaySettings\",\"PartnerRequirement\",\"PartnerSafeHeartbeatTelemetry\",\"PartnerSelfPromoAd\",\"PartnerSelfPromoCreate\",\"PartnerSelfPromoPlacement\",\"PartnerStats\",\"PartnerTripState\",\"PartnerVariants\",\"PartnerZoneCpmDaily\",\"PayoutResponse\",\"PlaygroundResult\",\"PlaygroundValidationError\",\"ProgrammaticSettingsInput\",\"ProgrammaticSettingsResponse\",\"RequestMetadataResponse\",\"ResolvedContentPolicy\",\"ResolvedSensingConfig\",\"RolePermissions\",\"SDKConfig\",\"Screen\",\"ScreenAnalyticsResponse\",\"ScreenGroup\",\"ScreenGroupContentPolicy\",\"ScreenGroupContentPolicyInput\",\"ScreenGroupEvent\",\"ScreenGroupSensingConfig\",\"ScreenGroupSensingConfigInput\",\"SensingPerformanceMode\",\"SensingRoute\",\"SensingRouteAction\",\"SensingRouteCondition\",\"SensingRouteReviewTimingMode\",\"SensingRouteReviewWaitSeconds\",\"SensingRouteScreenFocusRegion\",\"SensingSettingsResponse\",\"SensingThresholdCrossedEvent\",\"SsdpDeviceInfo\",\"TeamInviteResponse\",\"TeamMember\",\"Transaction\",\"TrillboardsError\",\"UpdatePartnerSensingRouteRequest\",\"VastWaterfall\",\"VastWaterfallSource\",\"Webhook\",\"WebhookDelivery\",\"WebhookEvent\",\"WifiEnvironmentSnapshot\",\"WifiScanResult\"] as const,\n operations: [\"queryChangelog\",\"dataApiAdSightings\",\"dataApiAdSightingsHistory\",\"dataApiAggregate\",\"dataApiLookalikeScreens\",\"dataApiQueryDsl\",\"dataApiListScreens\",\"dataApiScreenAudienceLive\",\"dataApiScreenAudienceHistory\",\"dataApiSemanticSearch\",\"dataApiStats\",\"dataApiStream\",\"dataApiUsage\",\"listErrorCodes\",\"getErrorCode\",\"exampleHealthcheck\",\"listPartnerSelfPromoAds\",\"createPartnerSelfPromoAd\",\"listPartnerSelfPromoPlacements\",\"schedulePartnerSelfPromo\",\"createPartnerSelfPromoUpload\",\"disableSelfPromoAd\",\"registerAgent\",\"getAnalytics\",\"getAudienceContinuity\",\"getAudienceProfile\",\"getBleSummary\",\"getContentPerformance\",\"searchCreativeCatalog\",\"getCreativeObservations\",\"getCreativeScenes\",\"getCrossScreenFlow\",\"getPartnerAnalyticsDashboard\",\"getDeviceSuiteSignature\",\"exportPartnerAnalytics\",\"exportPartnerAdsByDay\",\"getFleetHourly\",\"getHaloFunnel\",\"getLoyaltyClock\",\"getNetworkSummary\",\"getNetworkTrend\",\"getProximitySummary\",\"getRevenueAttentionCorrelation\",\"getSceneFeed\",\"getSceneIntelligence\",\"rotateApiKey\",\"getAudienceAnalytics\",\"exportAudienceData\",\"getAudienceHeatmap\",\"getLiveAudience\",\"getLookalikeScreens\",\"predictScreenAudience\",\"getResolvedHouseholdReach\",\"listAudienceSegments\",\"createAudienceSegment\",\"deleteAudienceSegment\",\"getAudienceSegment\",\"updateAudienceSegment\",\"matchAudienceSegment\",\"estimateSegmentReach\",\"generateSegmentVastTag\",\"getAudienceStats\",\"cancelBillingSubscription\",\"purchaseCredits\",\"createCreditCheckout\",\"listBillingPlans\",\"setupBilling\",\"createBillingSubscription\",\"getBillingSubscription\",\"getBillingUsage\",\"getBillingUsageSummary\",\"stripeWebhookCallback\",\"getConnectDashboard\",\"getConnectEarningsChart\",\"getConnectScreenHealth\",\"listContentPresets\",\"getContentPreset\",\"createCreativeFeedbackExperiment\",\"getCreativeFeedbackExperiment\",\"configureCreativeFeedbackBrand\",\"generateCreativeFeedbackCreative\",\"importCreativeFeedbackCreative\",\"reviewCreativeFeedbackCreative\",\"updateCreativeFeedbackPolicy\",\"getCreativeFeedbackRewards\",\"stopCreativeFeedbackExperiment\",\"registerCreativeFeedbackVariants\",\"submitPartnerCreative\",\"getPartnerCreative\",\"getDashboard\",\"exportProximityGraphEdges\",\"getDataExportSummary\",\"exportDataset\",\"getGeohashAudiencePanel\",\"getOutcomeAttribution\",\"registerDevice\",\"deleteDevice\",\"getDevice\",\"updateDevice\",\"getAdsForDevice\",\"ackDeviceCommand\",\"getDeviceHeartbeat\",\"heartbeat\",\"updateDeviceImpressionMultiplier\",\"recordPartnerDeviceInteraction\",\"recordPartnerDevicePlay\",\"recordProgrammaticEvent\",\"recordPartnerDeviceTripState\",\"getVastForDevice\",\"listDevices\",\"registerDeviceV2\",\"registerDevicesBatch\",\"batchUpdateDeviceOrientation\",\"reconcileDevices\",\"partnerDocsBrowser\",\"getEarnings\",\"getEarningsBreakdown\",\"requestPayout\",\"getTransactions\",\"getActiveSSEConnections\",\"subscribeToEvents\",\"getFleetCommand\",\"getFleetAnalytics\",\"pushFleetCommand\",\"getFleetOverview\",\"updateFleetSettings\",\"recordImpressionGet\",\"recordImpressionPost\",\"recordImpressionsBatch\",\"getPartnerInfo\",\"listIntentCategories\",\"getIntentCategory\",\"getIntentPricing\",\"getMeasurementMethodology\",\"getMeasurementMethodologyJson\",\"getNetworkConfig\",\"patchNetworkConfig\",\"updateNetworkConfig\",\"getNetworkTrends\",\"partnerPing\",\"partnerPingAuth\",\"recordPartnerPlay\",\"recordPartnerPlaysBatch\",\"getPricing\",\"recordProgrammaticEventLegacyAlias\",\"quickStart\",\"registerPartner\",\"generateSandboxEvent\",\"resetSandbox\",\"createSandboxScreen\",\"getSandboxStatus\",\"listScreenGroups\",\"createScreenGroup\",\"deleteScreenGroup\",\"getScreenGroup\",\"updateScreenGroup\",\"getScreenGroupAnalytics\",\"createScreenGroupCommand\",\"getScreenGroupContentPolicy\",\"updateScreenGroupContentPolicy\",\"removeScreenGroupMembers\",\"listScreenGroupMembers\",\"addScreenGroupMembers\",\"getScreenGroupSensingConfig\",\"updateScreenGroupSensingConfig\",\"getScreenAudienceSegments\",\"batchUpdateContentPreferences\",\"batchUpdateDisplaySettings\",\"batchUpdateProgrammaticSettings\",\"resolvePartnerScreen\",\"getScreenAnalytics\",\"getPartnerScreenAtmosphere\",\"getPartnerScreenIntelligence\",\"getPartnerScreenTasteProfile\",\"getPartnerScreenTraffic\",\"getPartnerScreenVAS\",\"applyContentPreset\",\"getAudienceMetrics\",\"getContentPreferences\",\"patchContentPreferences\",\"updateContentPreferences\",\"listPartnerDefaultStreams\",\"createPartnerDefaultStream\",\"createPartnerDefaultStreamImages\",\"reorderPartnerDefaultStreams\",\"createPartnerDefaultStreamUpload\",\"deletePartnerDefaultStream\",\"updatePartnerDefaultStream\",\"getScreenDemographics\",\"getDisplaySettings\",\"patchDisplaySettings\",\"updateDisplaySettings\",\"getPartnerLBarSettings\",\"updatePartnerLBarSettings\",\"updatePartnerLBarLiveUpdates\",\"getProgrammaticSettings\",\"patchProgrammaticSettings\",\"updateProgrammaticSettings\",\"getResolvedContentPolicy\",\"getResolvedSensingConfig\",\"getScreenSensingChips\",\"interpretScreenSensingIntent\",\"deployScreenSensingProfile\",\"getScreenSensingSettings\",\"updateScreenSensingSettings\",\"listPartnerSensingRoutes\",\"createPartnerSensingRoute\",\"deletePartnerSensingRoute\",\"getPartnerSensingRoute\",\"updatePartnerSensingRoute\",\"createStripeConnectAccount\",\"getStripeLoginLink\",\"getStripeOnboardingLink\",\"requestStripePayout\",\"getStripePayoutHistory\",\"getStripeAccountStatus\",\"handleStripeWebhook\",\"listTeamMembers\",\"acceptInvitation\",\"inviteTeamMember\",\"getInvitationByToken\",\"resendInvitation\",\"removeTeamMember\",\"changeTeamMemberRole\",\"getTrackingPublicKey\",\"recordTrackingBatch\",\"getUsageSummary\",\"getUsageDashboard\",\"getRateLimitStatus\",\"getVastConfig\",\"getVenuesNearby\",\"getVenueAtmosphere\",\"getVenueTraffic\",\"getVenueIntelligence\",\"listWebhooks\",\"createWebhook\",\"deleteWebhook\",\"updateWebhook\",\"getWebhookDeliveries\",\"testWebhook\",\"playgroundDryRun\",\"getRequestMetadata\"] as const,\n} as const;\n\nexport type GeneratedSchemaName = (typeof generatedTypes.schemas)[number];\nexport type GeneratedOperationName = (typeof generatedTypes.operations)[number];\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/generated/index.ts"],"names":[],"mappings":";;;AAunVO,IAAM,cAAA,GAAiB;AAAA,EAC5B,WAAA,EAAa,GAAA;AAAA,EACb,cAAA,EAAgB,GAAA;AAAA,EAChB,OAAA,EAAS,CAAC,IAAA,EAAK,YAAA,EAAa,uBAAsB,eAAA,EAAgB,2BAAA,EAA4B,+BAA8B,cAAA,EAAe,wBAAA,EAAyB,8BAA6B,yBAAA,EAA0B,iBAAA,EAAkB,2BAA0B,aAAA,EAAc,aAAA,EAAc,qBAAoB,gBAAA,EAAiB,gBAAA,EAAiB,qBAAoB,sBAAA,EAAuB,oBAAA,EAAqB,2BAA0B,4BAAA,EAA6B,eAAA,EAAgB,oCAAmC,wBAAA,EAAyB,2BAAA,EAA4B,qBAAoB,0BAAA,EAA2B,oCAAA,EAAqC,+BAA8B,2BAAA,EAA4B,eAAA,EAAgB,qBAAoB,oCAAA,EAAqC,6BAAA,EAA8B,6BAA4B,sBAAA,EAAuB,0BAAA,EAA2B,uBAAsB,sBAAA,EAAuB,gCAAA,EAAiC,+BAAA,EAAgC,6BAAA,EAA8B,gCAAA,EAAiC,uBAAA,EAAwB,0BAAyB,uBAAA,EAAwB,2BAAA,EAA4B,kBAAiB,sBAAA,EAAuB,QAAA,EAAS,qBAAoB,yBAAA,EAA0B,eAAA,EAAgB,wBAAuB,yBAAA,EAA0B,eAAA,EAAgB,6BAA4B,cAAA,EAAe,kBAAA,EAAmB,mBAAkB,OAAA,EAAQ,WAAA,EAAY,qBAAoB,YAAA,EAAa,eAAA,EAAgB,eAAA,EAAgB,mBAAA,EAAoB,WAAA,EAAY,kBAAA,EAAmB,iBAAgB,sBAAA,EAAuB,uBAAA,EAAwB,yBAAwB,wBAAA,EAAyB,oBAAA,EAAqB,4BAA2B,kCAAA,EAAmC,4BAAA,EAA6B,2BAA0B,0BAAA,EAA2B,2BAAA,EAA4B,yBAAwB,uBAAA,EAAwB,2BAAA,EAA4B,mCAAkC,uBAAA,EAAwB,wBAAA,EAAyB,uBAAsB,mBAAA,EAAoB,iBAAA,EAAkB,yBAAA,EAA0B,qBAAA,EAAsB,iBAAA,EAAkB,cAAA,EAAe,qBAAoB,uBAAA,EAAwB,iBAAA,EAAkB,eAAc,YAAA,EAAa,kBAAA,EAAmB,eAAc,2BAAA,EAA4B,6BAAA,EAA8B,mCAAkC,yBAAA,EAA0B,uBAAA,EAAwB,0BAAyB,wBAAA,EAAyB,yBAAA,EAA0B,6BAA4B,mBAAA,EAAoB,wBAAA,EAAyB,8BAA6B,YAAA,EAAa,SAAA,EAAU,0BAAA,EAA2B,yBAAA,EAA0B,sBAAA,EAAuB,4BAAA,EAA6B,8BAA6B,qCAAA,EAAsC,4BAAA,EAA6B,qBAAoB,gCAAA,EAAiC,yBAAA,EAA0B,wBAAuB,qCAAA,EAAsC,2BAAA,EAA4B,iCAAgC,yBAAA,EAA0B,0BAAA,EAA2B,6BAA4B,oBAAA,EAAqB,oBAAA,EAAqB,iCAAgC,qBAAA,EAAsB,2BAAA,EAA4B,uBAAsB,iCAAA,EAAkC,sBAAA,EAAuB,4BAAA,EAA6B,aAAA,EAAc,8BAAA,EAA+B,oCAAA,EAAqC,sBAAqB,+BAAA,EAAgC,oBAAA,EAAqB,0BAAyB,2BAAA,EAA4B,cAAA,EAAe,oBAAmB,iBAAA,EAAkB,qBAAA,EAAsB,kBAAiB,kBAAA,EAAmB,2BAAA,EAA4B,6BAA4B,8BAAA,EAA+B,yBAAA,EAA0B,yBAAwB,uBAAA,EAAwB,iBAAA,EAAkB,aAAY,QAAA,EAAS,yBAAA,EAA0B,aAAA,EAAc,0BAAA,EAA2B,+BAAA,EAAgC,kBAAA,EAAmB,4BAA2B,+BAAA,EAAgC,wBAAA,EAAyB,gBAAe,oBAAA,EAAqB,uBAAA,EAAwB,gCAA+B,+BAAA,EAAgC,+BAAA,EAAgC,2BAA0B,8BAAA,EAA+B,gBAAA,EAAiB,sBAAqB,YAAA,EAAa,aAAA,EAAc,oBAAmB,kCAAA,EAAmC,eAAA,EAAgB,uBAAsB,SAAA,EAAU,iBAAA,EAAkB,cAAA,EAAe,yBAAA,EAA0B,gBAAgB,CAAA;AAAA,EACp6I,UAAA,EAAY,CAAC,gBAAA,EAAiB,oBAAA,EAAqB,2BAAA,EAA4B,kBAAA,EAAmB,yBAAA,EAA0B,iBAAA,EAAkB,oBAAA,EAAqB,2BAAA,EAA4B,8BAAA,EAA+B,uBAAA,EAAwB,cAAA,EAAe,iBAAgB,cAAA,EAAe,gBAAA,EAAiB,cAAA,EAAe,oBAAA,EAAqB,yBAAA,EAA0B,0BAAA,EAA2B,gCAAA,EAAiC,0BAAA,EAA2B,8BAAA,EAA+B,oBAAA,EAAqB,eAAA,EAAgB,cAAA,EAAe,uBAAA,EAAwB,oBAAA,EAAqB,iBAAgB,uBAAA,EAAwB,uBAAA,EAAwB,yBAAA,EAA0B,mBAAA,EAAoB,oBAAA,EAAqB,8BAAA,EAA+B,yBAAA,EAA0B,wBAAA,EAAyB,uBAAA,EAAwB,gBAAA,EAAiB,eAAA,EAAgB,iBAAA,EAAkB,mBAAA,EAAoB,iBAAA,EAAkB,qBAAA,EAAsB,gCAAA,EAAiC,cAAA,EAAe,sBAAA,EAAuB,cAAA,EAAe,sBAAA,EAAuB,oBAAA,EAAqB,oBAAA,EAAqB,iBAAA,EAAkB,qBAAA,EAAsB,uBAAA,EAAwB,2BAAA,EAA4B,sBAAA,EAAuB,uBAAA,EAAwB,uBAAA,EAAwB,oBAAA,EAAqB,yBAAwB,sBAAA,EAAuB,sBAAA,EAAuB,wBAAA,EAAyB,kBAAA,EAAmB,2BAAA,EAA4B,iBAAA,EAAkB,sBAAA,EAAuB,kBAAA,EAAmB,cAAA,EAAe,2BAAA,EAA4B,wBAAA,EAAyB,iBAAA,EAAkB,wBAAA,EAAyB,uBAAA,EAAwB,qBAAA,EAAsB,yBAAA,EAA0B,wBAAA,EAAyB,oBAAA,EAAqB,kBAAA,EAAmB,kCAAA,EAAmC,+BAAA,EAAgC,gCAAA,EAAiC,kCAAA,EAAmC,gCAAA,EAAiC,gCAAA,EAAiC,8BAAA,EAA+B,4BAAA,EAA6B,gCAAA,EAAiC,kCAAA,EAAmC,uBAAA,EAAwB,sBAAqB,cAAA,EAAe,2BAAA,EAA4B,sBAAA,EAAuB,eAAA,EAAgB,yBAAA,EAA0B,uBAAA,EAAwB,gBAAA,EAAiB,cAAA,EAAe,WAAA,EAAY,cAAA,EAAe,iBAAA,EAAkB,kBAAA,EAAmB,oBAAA,EAAqB,WAAA,EAAY,kCAAA,EAAmC,gCAAA,EAAiC,yBAAA,EAA0B,yBAAA,EAA0B,8BAAA,EAA+B,kBAAA,EAAmB,aAAA,EAAc,kBAAA,EAAmB,sBAAA,EAAuB,8BAAA,EAA+B,kBAAA,EAAmB,oBAAA,EAAqB,aAAA,EAAc,sBAAA,EAAuB,eAAA,EAAgB,iBAAA,EAAkB,2BAA0B,mBAAA,EAAoB,iBAAA,EAAkB,mBAAA,EAAoB,kBAAA,EAAmB,kBAAA,EAAmB,qBAAA,EAAsB,qBAAA,EAAsB,sBAAA,EAAuB,wBAAA,EAAyB,gBAAA,EAAiB,sBAAA,EAAuB,mBAAA,EAAoB,kBAAA,EAAmB,2BAAA,EAA4B,+BAAA,EAAgC,kBAAA,EAAmB,oBAAA,EAAqB,qBAAA,EAAsB,kBAAA,EAAmB,aAAA,EAAc,iBAAA,EAAkB,mBAAA,EAAoB,yBAAA,EAA0B,YAAA,EAAa,oCAAA,EAAqC,YAAA,EAAa,iBAAA,EAAkB,sBAAA,EAAuB,cAAA,EAAe,qBAAA,EAAsB,oBAAmB,kBAAA,EAAmB,mBAAA,EAAoB,mBAAA,EAAoB,gBAAA,EAAiB,mBAAA,EAAoB,yBAAA,EAA0B,0BAAA,EAA2B,6BAAA,EAA8B,gCAAA,EAAiC,0BAAA,EAA2B,wBAAA,EAAyB,uBAAA,EAAwB,6BAAA,EAA8B,gCAAA,EAAiC,2BAAA,EAA4B,+BAAA,EAAgC,4BAAA,EAA6B,iCAAA,EAAkC,sBAAA,EAAuB,oBAAA,EAAqB,4BAAA,EAA6B,8BAAA,EAA+B,8BAAA,EAA+B,yBAAA,EAA0B,qBAAA,EAAsB,oBAAA,EAAqB,oBAAA,EAAqB,uBAAA,EAAwB,yBAAA,EAA0B,0BAAA,EAA2B,6BAA4B,4BAAA,EAA6B,kCAAA,EAAmC,8BAAA,EAA+B,kCAAA,EAAmC,4BAAA,EAA6B,4BAAA,EAA6B,uBAAA,EAAwB,oBAAA,EAAqB,sBAAA,EAAuB,uBAAA,EAAwB,wBAAA,EAAyB,2BAAA,EAA4B,8BAAA,EAA+B,yBAAA,EAA0B,2BAAA,EAA4B,4BAAA,EAA6B,0BAAA,EAA2B,0BAAA,EAA2B,uBAAA,EAAwB,8BAAA,EAA+B,4BAAA,EAA6B,0BAAA,EAA2B,6BAAA,EAA8B,0BAAA,EAA2B,2BAAA,EAA4B,2BAAA,EAA4B,wBAAA,EAAyB,2BAAA,EAA4B,4BAAA,EAA6B,oBAAA,EAAqB,2BAA0B,qBAAA,EAAsB,wBAAA,EAAyB,wBAAA,EAAyB,qBAAA,EAAsB,iBAAA,EAAkB,kBAAA,EAAmB,kBAAA,EAAmB,sBAAA,EAAuB,kBAAA,EAAmB,kBAAA,EAAmB,sBAAA,EAAuB,sBAAA,EAAuB,qBAAA,EAAsB,iBAAA,EAAkB,mBAAA,EAAoB,oBAAA,EAAqB,eAAA,EAAgB,iBAAA,EAAkB,oBAAA,EAAqB,iBAAA,EAAkB,sBAAA,EAAuB,cAAA,EAAe,eAAA,EAAgB,eAAA,EAAgB,eAAA,EAAgB,sBAAA,EAAuB,aAAA,EAAc,kBAAA,EAAmB,oBAAoB;AAC3/K","file":"generated.js","sourcesContent":["/* eslint-disable */\n/**\n * AUTO-GENERATED. DO NOT EDIT.\n *\n * Generated from: merged partner API, bundled Data API and mounted MCP Zod schemas\n * Generator: trillboards-api-client/scripts/generate.js\n *\n * Re-emit via `npm run generate`.\n */\n\n// OpenAPI version: 3.0.3\n// API title: Trillboards Partner API\n// API version: 1.2.0\n// Schema count: 194\n// Operation count: 242\n\n// ─── Component schemas ───────────────────────────────────────────────────\n\nexport type Ad = {\n /** Placement/allocation ID used for delivery accounting, make-goods, and pacing. */\n allocation_id?: string;\n /** Duration in seconds */\n duration?: number;\n /** Advertisement ID */\n id?: string;\n /** Server-generated impression ID that must be echoed when reporting proof-of-play. */\n impression_id?: string;\n /** **Deprecated alias of `tracking.impression_url`.** These two fields are the same string, byte for byte, produced from one value server-side — they are not two different hooks and firing both records the same call twice. Read `tracking.impression_url`; this flat field is retained only for clients that predate the `tracking` object and will be removed once they are off it. */\n impression_url?: string;\n /** The advertiser's own creative label from the booking record (its `title`, falling back to its `name`), carried through for your logs and QA. Omitted when the booking has neither. It is an internal label, not reviewed display copy, so do not surface it to viewers. */\n title?: string;\n /** Structured proof-of-play URLs for clients that prefer named tracking hooks over legacy flat fields. **Fire exactly one of `impression_url` / `complete_url` per play.** They resolve to the same endpoint and differ only by the `completed` query parameter, which that endpoint does not act on (see `GET /impression`) — calling both records the play twice. */\n tracking?: {\n /** Call when the ad completes playback/display dwell. This is `impression_url` plus `&completed=true`; the flag is inert server-side, so this is a delivery-accounting call, not proof of play. */\n complete_url?: string;\n /** Call when the ad is presented. Identical to the top-level `impression_url`. */\n impression_url?: string;\n /** **Server-signed proof of play. Present only on owner-billable direct bookings; absent on self-promo, house/QR, demo and fully-delivered placements.** When present, fire it VERBATIM — including the `sig`, `request_id` and `impid` parameters — once the ad has finished playing. Do not rewrite, re-sign, re-order, or drop any query parameter: the signature covers the values and any edit invalidates it. This is the only completion signal on the `data.ads` rail that settles; `complete_url` above is delivery accounting and is not billing-grade. Replays of the same play are collapsed on the SIGNED `request_id`, which carries this serve's `impression_id`. Because it is covered by `sig`, a replay that alters it to claim a fresh dedup key fails verification and records nothing — one served play can settle at most once. Firing it in addition to `complete_url` is correct and does not double-count — they land in different stores. */\n owned_direct_complete_url?: string;\n };\n /** `video` or `image`. On the partner/IMA rail an image instant-ad creative is substituted with its pre-rendered MP4 and reports `video`; seeing `image` here means that MP4 was not ready on this poll, so a video-only player should skip the entry and pick it up on a later poll rather than treat it as a still. */\n type?: \"video\" | \"image\";\n /** Media URL (CDN) */\n url?: string;\n};\n\nexport type AdSettings = {\n /** Partner-configured ad rotation cadence in seconds, served from the screen display setting `ad_interval_seconds` when present and legacy display preferences only as fallback. */\n ad_interval?: number;\n content_fit?: ContentFitSettings;\n /** Seconds to display image ads; independent from ad rotation cadence. */\n image_duration?: number;\n /** An ADVISORY delivery cap the client SDK is expected to honour — the server reports it and never enforces it anywhere. Sourced from the partner's `network_config.ad_delivery.max_ads_per_hour` (an integer 1-120), falling back to `sdk_config.max_ads_per_hour`; when neither is set the key is ABSENT from the response rather than null or defaulted. */\n max_ads_per_hour?: number;\n /** Label for the tap-to-continue overlay the partner Lite SDK draws over an ad. Read from `sdk_config.overlay_button_text` only — there is no runtime-config path for it — and the key is ABSENT when unset, in which case the SDK falls back to `TAP TO CONTINUE` client-side. */\n overlay_button_text?: string;\n /** Whether ad audio plays. It reads the typed `sound_enabled` column when that is non-null, otherwise `display_preferences.sound_enabled !== false` — so an unconfigured screen reports `true` and only an explicit `false` mutes. */\n sound_enabled?: boolean;\n};\n\n/**\n * Stateful resettable operating-system advertising identity. Send\n * `available` only with the current OS ID; send `limited`,\n * `consent_denied`, `unavailable`, `unsupported`, or `error` to clear a\n * previously observed identifier. UID2 and hashed email are audience\n * tokens, not OS advertising IDs, and do not belong in this envelope.\n *\n */\nexport type AdvertisingIdentity = {\n /** DISCARDED whenever `status` is `available` — the server stores `null` in that case, deliberately, because a resettable OS advertising ID stays valid until the OS rotates it and a collector's refresh or cache cadence is not identity expiry. It is only persisted for the non-`available` states. */\n expires_at?: string | null;\n /** Present only when status is `available`. */\n id?: string | null;\n /** Resettable OS identifier type. GAID maps to OpenRTB `aaid`. */\n id_type?: \"gaid\" | \"aaid\" | \"afai\" | \"rida\" | \"tifa\" | \"idfa\" | null;\n /** Must be false for `available` and true for `limited`. */\n limit_ad_tracking: boolean | null;\n /** When the device read the identifier from the OS. It is REQUIRED and strictly parsed — an unparseable or missing timestamp causes the entire identity envelope to be dropped without an error response, which is the most common silent rejection on this endpoint. */\n observed_at: string;\n /** Which platform API produced the identifier. `host_supplied` is the value the SERVER itself stamps when it synthesises an envelope from the legacy flat `advertisingId`/`limitAdTracking` fields or from a registration hardware tuple, so seeing it in a read does not mean the device claimed it. An unrecognised source rejects the whole envelope. */\n source: \"google_play_services\" | \"amazon_fire_os\" | \"roku_os\" | \"samsung_tizen\" | \"lg_webos\" | \"apple_platform\" | \"host_supplied\";\n /** Version of the platform component or SDK that read the identifier (e.g. the Play Services version), up to 128 characters, kept for provenance only. Trimmed, with a whitespace-only value stored as `null`. */\n source_version?: string | null;\n /** The state machine, not a label. `available` is accepted only with a valid `id`, an `id_type` and `limit_ad_tracking: false` — any other combination makes the server DISCARD the whole envelope behind a 200 and keep the previously stored identity. Every other value CLEARS a stored identifier, and `limited` additionally forces `limit_ad_tracking` to true. */\n status: \"available\" | \"limited\" | \"consent_denied\" | \"unavailable\" | \"unsupported\" | \"error\";\n};\n\n/**\n * Partner analytics summary (`@trillboards/api-client` `AnalyticsData`).\n *\n */\nexport type AnalyticsData = {\n /** **Never emitted** by the analytics endpoint, which returns impression counts and cached revenue only. */\n average_cpm: number;\n /** Optional per-screen breakdown. */\n by_screen?: Array<{\n earnings: number;\n impressions: number;\n name: string;\n screen_id: string;\n }>;\n /** Fraction of ad opportunities filled, 0..1. */\n fill_rate: number;\n /** The window this summary covers (e.g. day, week, month). */\n period: string;\n /** **Not emitted under this name.** The analytics endpoint returns `data.partner.active_devices`, derived from the screen's `online_status` via the partner device-status mapping, plus a `devices_by_status` breakdown. */\n screens_active: number;\n /** **Not emitted under this name.** The analytics endpoint returns `data.partner.total_devices`, a count of registered DEVICES rather than of screens — a device with no screen row still counts. */\n screens_total: number;\n /** **Not emitted.** `GET /v1/partner/analytics` returns `data.partner.total_revenue_cents` (integer US cents, read from the `partners.stats` cache) instead; that endpoint has no dollar-denominated earnings field. */\n total_earnings: number;\n /** Declared for the SDK type; `GET /v1/partner/analytics` does not return this shape. It returns `data.partner.total_impressions` — an ALL-TIME deduped `completed_impressions` count that ignores the `start_date`/`end_date` query parameters (those bound only `impressions_by_day`). */\n total_impressions: number;\n};\n\nexport type AudienceAnalyticsResponse = {\n /** `overview.total_impressions` counts `event_tracking` `start`/`impression` events — NOT the billable `completed_impressions` the dashboard and earnings endpoints use — and `active_screens` counts screens with ANY event in the window rather than screens that are online. **`trends.impressions_trend` and `trends.audience_trend` are hardcoded to the literal `stable`**, so the `increasing`/`decreasing` values are unreachable. `period` echoes `?period=` (`24h`/`7d`/`30d`; anything else silently means 7 days). */\n data?: {\n audience?: {\n avg_attention?: number;\n avg_face_count?: number;\n screens_with_data?: number;\n };\n overview?: {\n active_screens?: number;\n avg_impressions_per_screen?: number;\n total_impressions?: number;\n total_screens?: number;\n };\n period?: string;\n timestamp?: string;\n trends?: {\n audience_trend?: \"increasing\" | \"stable\" | \"decreasing\";\n impressions_trend?: \"increasing\" | \"stable\" | \"decreasing\";\n };\n };\n /** Per-request identifier (`req_` plus 32 hex), also returned on the `X-Request-Id` response header. */\n request_id?: string;\n /** Always `true` on a 2xx; failures return the error envelope (`success: false` plus an `error` object) at a 4xx/5xx status. */\n success?: boolean;\n};\n\n/**\n * Backend-derived predictive attention signal + buyer-grade attentive-seconds. 1:1 with audience_metrics. Never null by contract; placeholder on row 1.\n */\nexport type AudienceAttentionPrediction = {\n /** attentive_face_count SMALLINT: distinct viewers meeting the >=1s threshold; the buyer-side CPMA denominator. NEVER NULL. */\n attentive_face_count: number;\n /** attentive_seconds_observed = SUM(per_face[].gaze_seconds >= 1.0), OpenOOH 2024 4.2. NEVER NULL by DDL, so 0.000 means 'no viewer met threshold' ONLY when per_face_observations[] was present. NUMERIC(6,3). */\n attentive_seconds_observed: number;\n /** attentive_seconds_predicted_15s: expected next-15s attentive seconds from per-screen history x attention-weight; bootstrap = linear extrapolation. No global tau. NUMERIC(6,3). */\n attentive_seconds_predicted_15s: number;\n next_state_abs_error?: number | null;\n next_state_attention_forecast?: number | null;\n next_state_attention_realized?: number | null;\n next_state_confidence?: number | null;\n next_state_forecast_at?: string | null;\n next_state_graded_at?: string | null;\n next_state_horizon_minutes?: number | null;\n next_state_method?: string | null;\n next_state_persist_abs_error?: number | null;\n next_state_sample_size?: number | null;\n next_state_xscreen_abs_error?: number | null;\n next_state_xscreen_method?: string | null;\n next_state_xscreen_sample_size?: number | null;\n next_state_xsv_abs_error?: number | null;\n next_state_xsv_sample_size?: number | null;\n /** predicted_attention_next_30s enum {high,medium,low,none}. 'none' is below 'low'; fires when rolling history converges to zero or on a trend down from 'low'. */\n predicted_attention_next_30s: \"high\" | \"medium\" | \"low\" | \"none\";\n /** predicted_disengagement_imminent needs elevated dwell AND a falling trend, so it is unreachable whenever the history vector is empty. A 0% fire rate is a HISTORY-PLUMBING alarm, not a quiet fleet. */\n predicted_disengagement_imminent: boolean;\n prediction_evidence: string;\n};\n\n/**\n * Live/aggregate audience data for a screen\n * (`@trillboards/api-client` `AudienceData`).\n *\n */\nexport type AudienceData = {\n /** Count of attentive viewers in the sample. */\n attention_count: number;\n /** Optional demographic breakdown. */\n demographics?: Record<string, unknown>;\n /** Average dwell time for the sample. */\n dwell_time_avg: number;\n /** Declared for the SDK type. `GET /v1/partner/audience/live/{screenId}` puts `screen_id` at the TOP level of an `object: audience_live` envelope alongside `status` and `audience`, not inside this shape. */\n screen_id: string;\n /** **Not emitted under this name.** The live audience endpoint returns `last_updated` — the time of the newest underlying observation, nullable — and returns `audience: null` with `status: offline` when the screen has no live data at all. */\n timestamp: string;\n};\n\nexport type AudienceExportResponse = {\n /** One entry per RESOLVED screen (max 50 ids per call), emitted even for screens with no audience data — those get a zeroed `audience` block rather than being omitted. `impressions` counts `event_tracking` `start`/`impression` events, which are NOT the billable `completed_impressions` the dashboard and earnings endpoints use, and every row repeats the same request-level `period`. */\n data?: Array<{\n audience?: {\n avg_attention?: number;\n avg_face_count?: number;\n demographics?: Record<string, unknown>;\n income_profile?: Record<string, unknown>;\n lifestyle_segments?: Array<string>;\n };\n country?: string;\n impressions?: number;\n location?: string;\n period?: {\n end?: string;\n start?: string;\n };\n screen_id?: string;\n screen_name?: string;\n }>;\n /** Per-request identifier (`req_` plus 32 hex), also on the `X-Request-Id` header. Absent from the `format=csv` variant, which returns a bare five-column body and no envelope. */\n request_id?: string;\n /** Always `true` — a failure returns the error envelope (`success: false` plus an `error` object) at a 4xx/5xx status, so this field carries nothing the status code does not. */\n success?: boolean;\n /** `total_screens` counts the screens that RESOLVED, so it can be smaller than the id list you sent. `total_impressions` sums the same non-billable `start`/`impression` event counts. `aggregation` echoes the query parameter unvalidated (default `daily`) and does not change the shape of the response. */\n summary?: {\n aggregation?: string;\n period?: Record<string, unknown>;\n total_impressions?: number;\n total_screens?: number;\n };\n};\n\n/**\n * Per-device cross-day loyalty feature row emitted by the\n * `audience_loyalty_features` dataset. Keyed by a partner-scoped\n * pseudonymous ID — the same physical device yields a different ID\n * for each partner because the HMAC salt is partner-specific: one salt\n * per partner, whichever API key calls, so the ID is the same ref the\n * partner's device-level files carry.\n *\n */\nexport type AudienceLoyaltyFeaturesRow = {\n /** How the cross-day device key was formed. `primary_signal_source` is picked by a fixed precedence (`fixed_mac` > `mdns_vendor_model` > `wifi_bssid` > `ble_continuity` > `ble_mac_stable`) and `match_confidence` is a direct relabelling of it (first three `high`, `ble_continuity` `medium`, `ble_mac_stable` `low`) — not a computed probability. `wifi_bssid` marks a device observed over WiFi, whose key is its BSSID. */\n _match_disclosure?: {\n match_confidence?: \"high\" | \"medium\" | \"low\";\n primary_signal_source?: \"ble_continuity\" | \"ble_mac_stable\" | \"mdns_vendor_model\" | \"fixed_mac\" | \"wifi_bssid\";\n };\n /** NOT a per-device inference: it is the aggregate audience profile of the first screen in the device's visited screen set that has one, copied onto every device that touched that screen. All six sub-fields are `null` when no visited screen carries a profile. */\n demographic_inference?: {\n age_bracket?: string;\n attention_score_avg?: number;\n audience_attire_archetype?: string;\n audience_group_composition?: string;\n audience_intent_stage?: string;\n gender_distribution?: Record<string, unknown>;\n };\n /** Partner-scoped HMAC of cross-day device key (16 hex chars + tb_ prefix). Different partners get different IDs for the same physical device — preventing collusion. */\n device_id_pseudonymous: string;\n /** Distinct calendar days (ClickHouse server timezone) on which this device key was observed. This is the quantity behind the published `returning_visitor` qualification of two or more days. */\n distinct_days_in_window: number;\n /** Distinct SCREENS, not venues — a site with several screens contributes more than one, so this over-counts locations for any multi-screen venue. */\n distinct_venues_in_window?: number;\n /** Days since this device was first observed at any of the partner-scoped screens. */\n first_seen_relative_days: number;\n /** Whole days between now and the newest observation in the window, floored at 0 — `0` means seen today. An unparseable source timestamp also yields `0`, so it cannot be told apart from a same-day sighting. */\n last_seen_relative_days: number;\n /** Tier derived from the Bluetooth-SIG company identifier in the device's advertisement, not from any model string. Only five vendors carry an explicit tier (Apple / Samsung / Google `premium`, Microsoft `mainstream`, Sonos `audio_iot`); every other registered vendor, and every unresolvable id, falls to `other`. */\n manufacturer_class: \"premium\" | \"mainstream\" | \"audio_iot\" | \"other\";\n /** Vendor name resolved from the same Bluetooth-SIG company id. An id that resolves to no vendor returns the literal `Other tracked devices` rather than `null`, so an unknown device is not distinguishable by a null check. */\n manufacturer_label?: string;\n /** Raw cross-day-stable identifiers when available (NULL for randomized phone MACs without Continuity). */\n raw_identifiers?: {\n ble_mac?: string | null;\n /** First 3 bytes of BLE MAC (IEEE OUI vendor lookup). */\n ble_mac_oui?: string | null;\n ble_mac_oui_vendor?: string | null;\n manufacturer_company_id?: number | null;\n mdns_hostname?: string | null;\n wifi_bssid?: string | null;\n };\n /** Same arbitrary-row behaviour as `top_hour_of_day`, and the value comes from ClickHouse `toDayOfWeek` — **1 = Monday through 7 = Sunday**, which does not fit the declared 0..6 range. */\n top_day_of_week?: number;\n /** Produced by `argMax(toHour(observed_at), 1)`; because the ordering expression is the constant `1` every row ties, so this is the hour of an ARBITRARY observation in the window, not the modal hour. Do not read a peak out of it. */\n top_hour_of_day?: number;\n /** List of venue_category labels (Bar, Restaurant, etc.) the device was observed at. */\n venue_archetypes?: Array<string>;\n /** Count of raw radio OBSERVATIONS, not of visits — one dwelling device emits many rows per minute. */\n visit_count_in_window: number;\n};\n\nexport type AudienceMetricsResponse = {\n /** **Currently always the zero/`unknown` fallback.** The handler reads `metrics.live`, `metrics.aggregated` and `metrics.history`, but the underlying `getScreenMetrics` takes a time-range STRING and returns a FLAT object carrying none of those keys — so all three are undefined on every call and the hardcoded zeros are served. `period` echoes `?period=` (default `1h`) and is likewise not honoured downstream; `timestamp` is the response time, not a measurement time. */\n data?: {\n aggregated?: {\n avgAttention?: number;\n avgDwellMs?: number;\n avgFaceCount?: number;\n groupComposition?: Record<string, unknown>;\n incomeProfile?: {\n high?: number;\n low?: number;\n medium?: number;\n premium?: number;\n };\n };\n history?: Array<Record<string, unknown>>;\n live?: {\n /** Average attention score (0-1) */\n avgAttention?: number;\n /** Average faces detected */\n avgFaceCount?: number;\n incomeLevel?: \"unknown\" | \"low\" | \"medium\" | \"high\" | \"premium\";\n /** Maximum faces detected in period */\n maxFaces?: number;\n mood?: string;\n screenEngagement?: string;\n };\n period?: string;\n screen_id?: string;\n screen_name?: string;\n timestamp?: string;\n };\n /** Per-request identifier (`req_` plus 32 hex), also returned on the `X-Request-Id` response header. */\n request_id?: string;\n /** Always `true` on a 2xx; failures return the error envelope (`success: false` plus an `error` object) at a 4xx/5xx status. */\n success?: boolean;\n};\n\nexport type AudienceSegment = {\n /** Creation time of the authoritative PG row; the 201 create response falls back to the server clock only if the insert returned no row. */\n created_at?: string;\n /** Free text, trimmed on write. Defaults to the empty string, never `null`, when omitted at create. */\n description?: string;\n /** 24-character hex ObjectId minted by the API at create time and stored as the PG row's `mongo_id` — not the table's integer primary key. This is the value the get / patch / delete / match routes take as `:segmentId`. */\n id?: string;\n /** Stored and echoed but **nothing reads it** — `POST /segments/{id}/match` runs a segment regardless, and the list endpoint does not filter on it. Treat it as a caller-owned label, not a gate. */\n is_active?: boolean;\n /** When `POST /segments/{id}/match` last ran. The update is fire-and-forget after the response is sent, so a failed write leaves this stale or `null` with no error surfaced to the caller. */\n last_matched_at?: string | null;\n /** Number of SCREENS returned by the most recent `POST /segments/{id}/match`, capped by that call's `limit` (default 50, max 100) — so it is the size of the last result page, not the segment's reach. `null` until the first match call. */\n last_matched_count?: number;\n /** Trimmed on write, max 100 characters, and unique per partner — a repeat name is rejected `409 duplicate_segment_name` rather than upserting the existing segment. */\n name?: string;\n /** Always the literal `audience_segment`. `GET /audience/segments` wraps these in an `object: list` envelope, so the discriminator appears on both the envelope and each element. */\n object?: string;\n /** Per-request identifier, `req_` plus 32 hex characters, minted server-side and also returned on the `X-Request-Id` response header. A client-supplied `X-Request-Id` is NOT adopted — it is echoed back on `X-Client-Request-Id` instead. */\n request_id?: string;\n /** Predicates combined with AND, at most 20 per segment. `field` is restricted to a 17-name allowlist (`income_level`, `lifestyle_segment`, `attention_score`, `face_count`, `dwell_time_ms`, `crowd_density`, `ad_receptivity`, `emotional_engagement`, `purchase_intent`, `occupancy`, `noise_level`, `venue_type`, `country`, `state`, `city`, `hour_of_day`, `day_of_week`) and `operator` to `eq`/`neq`/`gt`/`gte`/`lt`/`lte`/`in`/`nin`/`contains`; any other key on a rule object is stripped on write. */\n rules?: Array<{\n field?: string;\n operator?: string;\n value?: unknown;\n }>;\n /** Last write to the segment, INCLUDING the fire-and-forget match-stats update — so running a match bumps this even though name, rules and `is_active` did not change. Omitted entirely from the 201 create response. */\n updated_at?: string;\n};\n\n/**\n * Buyer-grade social amplification signal (5 typed columns). Cloud Vertex Gemini producer. 1 row per audience observation when cascade is observable.\n */\nexport type AudienceSocialAttention = {\n amplification_evidence: string;\n cascade_depth: number;\n data_projection_version: number;\n data_projection_xid: string;\n /** All 5 columns NOT NULL except primary_attention_anchor_face_index (only legit NULL: no anchor or solo scene). */\n primary_attention_anchor_face_index?: number | null;\n /** NUMERIC(4,3). */\n social_amplification_factor: number;\n /** viral_attention_score is the headline number Viant/T-Vision license. Composite of social_amplification_factor × cascade_depth × emotional engagement. NUMERIC(4,3). */\n viral_attention_score: number;\n};\n\nexport type BatchResult = {\n /** Number that failed */\n failed_count?: number;\n /** Not emitted. The live response names this array `errors`, and each entry carries `screen_id`, a human `error` string, and an additional machine `code`: `screen_not_found` for an id that is not 24-hex or that your partner account does not own, `write_failed` for a database failure. Read `errors`. */\n failed_screens?: Array<{\n error?: string;\n screen_id?: string;\n }>;\n /** Constant discriminator `batch_result`, shared verbatim by every partner batch endpoint (content preferences, display settings, programmatic settings) — it does not identify which one you called. */\n object?: string;\n /** Server-minted per HTTP request in the form `req_<32 hex>`, also returned in the `X-Request-Id` response header; omitted if no request id was assigned to the call. Quote it when reporting a partial batch failure — it is the only handle that ties your call to the per-screen error rows. */\n request_id?: string;\n /** Number of screens requested to update */\n total_requested?: number;\n /** Number successfully updated */\n updated_count?: number;\n /** IDs of successfully updated screens */\n updated_screens?: Array<string>;\n};\n\n/**\n * Partner billing snapshot — plan, credits, and month-to-date usage\n * (`@trillboards/api-client` `BillingInfo`).\n *\n */\nexport type BillingInfo = {\n /** **Never emitted**, so its unit is undefined. The credits that do exist are bought through `POST /billing/credits`, which reports `credits_added_cents` — integer US cents, not an abstract credit count. */\n credits_remaining: number;\n /** **Never emitted.** Consumption is reported by `GET /billing/usage` (`object: billing_usage`), which returns the billing service's usage summary rather than a credit counter. */\n credits_used: number;\n /** **No producer.** There is no `GET /v1/partner/billing` route; the live billing surface is `/billing/plans`, `/billing/subscribe`, `/billing/cancel`, `/billing/usage` and `/billing/subscription`, each returning a Stripe-style envelope tagged with an `object` marker plus `request_id`. */\n plan: string;\n /** Whether the partner has a connected Stripe account. */\n stripe_connected: boolean;\n /** Usage counters for the current billing month, keyed by metric. */\n usage_this_month: Record<string, number>;\n};\n\nexport type BleScanResultData = {\n /** Despite the name this is a GENERALISED advert-subtype code, and the server stores it as `manufacturer_subtype`. Values 0x00-0x1F are genuine Apple Continuity subtypes; 0x80 and above are SYNTHETIC markers this parser assigns to non-Apple formats — 0x80 iBeacon, 0x81 Eddystone-UID, 0x82 Fast Pair, 0x83 Tile, 0x85 Eddystone-URL, 0x86 AltBeacon. Note an Apple iBeacon reports 0x80, not its literal Apple subtype 0x02. */\n appleContinuitySubtype?: number | null;\n /** The Android `BluetoothDevice.getType()` constant: 0 UNKNOWN, 1 CLASSIC, 2 LE, 3 DUAL. Treat `0` as \"unknown OR the read threw\" — the producer catches any exception and substitutes 0 rather than omitting the field. */\n deviceType?: number | null;\n /** iBeacon major value, an unsigned 16-bit integer (0-65535) parsed BIG-endian from the advert. `null` unless a valid iBeacon frame was found, and meaningless without the accompanying `ibeaconUuid`. */\n ibeaconMajor?: number | null;\n /** iBeacon minor value, unsigned 16-bit big-endian (0-65535). `null` outside a valid iBeacon frame; major and minor are only comparable within one `ibeaconUuid` namespace. */\n ibeaconMinor?: number | null;\n /** The 16-byte iBeacon proximity UUID as canonical dashed lowercase, parsed BIG-endian. Present only for a well-formed Apple iBeacon frame — an Apple subtype 0x02 whose inner length is not 0x15 is explicitly rejected rather than partially parsed. */\n ibeaconUuid?: string | null;\n /** The Bluetooth SIG assigned company identifier from the advert's Manufacturer Specific Data structure (AD type 0xFF), read little-endian — e.g. 76 (0x004c) Apple, 6 Microsoft, 224 (0x00e0) Google. `null` when the record carries no such structure. One synthetic exception: a Google Fast Pair service-data advert is forced to the Google id even though it has no 0xFF structure. */\n manufacturerCompanyId?: number | null;\n /** The BLE advertiser address exactly as Android reports it, unhashed on the device. It is sent over TLS, and Trillboards stores it as received and as a keyed HMAC-SHA256 hash. On most modern phones this is a resolvable private address that ROTATES roughly every 15 minutes, so it is not a stable device identity; the sibling `addr_type` (`public`/`random`/`anonymous`) is what tells the two apart. */\n rawAddress: string;\n /** Received signal strength in dBm (negative, bounded -120..0). It is the MAXIMUM RSSI across every advertisement from this address inside the scan window — the closest observed reading, not a mean — because the scanner groups repeated adverts per address and keeps the strongest as the representative. */\n rssi: number;\n /** Advertised GATT service UUIDs, de-duplicated. 16-bit UUIDs are rendered as FOUR lowercase hex characters (e.g. `feaa`), not expanded to the full `0000feaa-0000-1000-8000-00805f9b34fb` base form; 128-bit UUIDs are canonical dashed lowercase. `null` rather than `[]` when the advert carries none. */\n serviceUuids?: Array<string> | null;\n /** Lowercase hex, no separators, of ONLY the advert sub-bytes that survive address rotation (an iBeacon UUID+major+minor, an Eddystone namespace+instance, a Fast Pair model id…), prefixed with the synthetic subtype byte. Calibration bytes such as TX power are deliberately excluded. It is `null` far more often than it is present: 13 rotating Apple subtypes are dropped outright and unknown Apple subtypes fail closed, so a populated `appleContinuitySubtype` with a null payload here is normal. */\n stableManufacturerPayloadHex?: string | null;\n /** Advertised transmit power in dBm (bounded -127..20) taken from the advert itself, NOT from `ScanResult.getTxPower()`. For iBeacon and Eddystone frames it is the format's calibrated 1-metre reference power, which overwrites the generic AD-type-0x0A value; `null` when the advert carries neither. */\n txPowerDbm?: number | null;\n [key: string]: unknown;\n};\n\nexport type CanonicalError = {\n error: {\n /** Stable machine-readable error code. Switch on this, never on `message`. The 143 CATALOGUED codes are published as the `ErrorCode` schema and served live by `GET /v1/errors`, which is the authority. This field is NOT limited to them: services emit further codes directly, and `ApiError.notFound(resource)` mints `<resource>_not_found` from an arbitrary resource name. Always handle an unrecognised code by falling back to `type`. */\n code: string;\n /** Per-field violations (populated from Zod errors). */\n details?: Array<{\n code?: string;\n message?: string;\n param?: string;\n }>;\n /** Link to the documentation for this error code (https://api.trillboards.com/docs/errors#<code>). */\n doc_url?: string;\n /** Agent-facing hints and suggestions. */\n help?: {\n portal_url?: string;\n suggestions?: Array<string>;\n summary?: string;\n try_next?: string;\n };\n /** Human-readable error message. Safe to surface to end-users. */\n message: string;\n /** Offending parameter name (when applicable). */\n param?: string;\n /** Echoed X-Request-Id for correlation. */\n request_id: string;\n /** Stripe-style error taxonomy (derived from utils/errorCatalog). */\n type: \"api_error\" | \"authentication_error\" | \"idempotency_error\" | \"invalid_request_error\" | \"permission_error\" | \"rate_limit_error\";\n };\n};\n\nexport type ChangelogEntry = {\n /** ISO date string or null for unreleased */\n date: string | null;\n /** Human-readable change description */\n description: string;\n /** API surface: data, sdk, device, sensing, openrtb, partner, all */\n surface: string;\n /** Keepachangelog type: added, changed, deprecated, removed, fixed, security */\n type: string;\n /** \"unreleased\" or a date like \"2026-04-16\" */\n version: string;\n};\n\nexport type ChangelogResponse = {\n /** Matching changelog entries */\n data: Array<ChangelogEntry>;\n /** Always \"list\" */\n object: \"list\";\n /** Number of entries returned */\n total: number;\n};\n\n/**\n * Completed-play observations from VAST complete and proof-of-play paths. Deduped by issued request/impression key for live revenue and daily earnings.\n */\nexport type CompletedImpressions = {\n /** NUMERIC(18,6). */\n actual_cpm?: number | null;\n /** NUMERIC(18,6). */\n actual_gross_revenue?: number | null;\n ad_id: string;\n advertiser_domain?: string | null;\n bid_request_id?: string | null;\n billable_key?: string | null;\n buyer_seat?: string | null;\n completed_at: string;\n /** Creative identity is NOT content_sha256 (35 of 430,165 ClickHouse rows since 2026-10-20): join creative_identity on the row's keys (identityKeys.identityJoinSql); its sidecar has the current and ti... */\n content_sha256?: string | null;\n /** chEventParityCron: exact-key repair; created_at/oldest authorized floor; original completed_at CH key; same writer/DLQ before ACK; no delivery debit. All leaf pending indexes must be valid; failed/... */\n created_at: string;\n creative_id?: string | null;\n currency: string;\n demand_partner_id?: number | null;\n device_id?: string | null;\n /** NUMERIC(5,4). */\n display_area_ratio?: number | null;\n display_mode?: string | null;\n /** NUMERIC(10,2). */\n duration_seconds?: number | null;\n earnings_deal_id?: string | null;\n earnings_deal_type?: string | null;\n earnings_source?: string | null;\n event_clock_skew_seconds?: number | null;\n event_timestamp_clamped?: boolean | null;\n givt_passed?: boolean | null;\n /** JustBaat dedupes issued request keys; Adipolo/VidVerto/others use rendered impression_id with request fallback. Readers/rollups dedupe overlapping vast_event, proof_of_play and approved partner_sdk. */\n id?: number | null;\n impression_id?: string | null;\n /** NUMERIC(12,6). */\n impression_multiplier: number;\n ingestion_path: string;\n /** SDK missing-price is server-pending until signed native pricing; directbillable_ is not authority. Client debit/mirror/pending claims are stripped. Legacy finalization: recorded/native nonnull zero... */\n metadata: Record<string, unknown>;\n normalized_event_timestamp?: string | null;\n play_id?: string | null;\n /** PG is the canonical completion write; completed_impressions_dist is its CH reporting mirror. pricing_source: 'placement_purchase'=ad_price/goal per play; '*_overdelivered'/'*_zero'=$0; 'config_defa... */\n pricing_source: string;\n quantity_measured_at?: string | null;\n quantity_source?: string | null;\n quantity_source_type?: number | null;\n quantity_vendor?: string | null;\n raw_client_timestamp?: string | null;\n /** owned_direct = signed src or verified SDK. VAST impid, request_id and SDK suffix share one nonce/PG row; completion, exact debit and eligible billing commit together. First authorized write owns CH... */\n request_id?: string | null;\n screen_id?: number | null;\n screen_mongo_id: string;\n serving_tenant_id?: number | null;\n signature?: string | null;\n /** ImpressionController drops rows with unknown source_provider (PR #2924); earnings use partner billing policy, not raw counts. Exclude pi_e2e_*, admin-allocation and is_demo_data from revenue. */\n source_provider: string;\n variant_id?: number | null;\n variant_name?: string | null;\n zone_id?: string | null;\n};\n\n/**\n * Per-content-type sizing returned and accepted by the Partner API.\n * `fit` preserves the entire creative and may letterbox; `fill` covers\n * the screen and may crop edges. Omitting this object from a write leaves\n * the screen's current behavior unchanged.\n *\n */\nexport type ContentFitSettings = {\n /** Sizing for direct and programmatic advertisements. */\n advertisements?: \"fit\" | \"fill\";\n /** Sizing for default/background streams. */\n default_streams?: \"fit\" | \"fill\";\n /** Sizing for self-promotion and QR-code house ads. */\n self_promo?: \"fit\" | \"fill\";\n};\n\nexport type ContentPreferencesInput = {\n /** Specific advertiser IDs to block */\n blocked_advertisers?: Array<string>;\n /** Ad categories to block (e.g., \"alcohol\", \"gambling\", \"adult\") */\n blocked_categories?: Array<string>;\n /** Max times same ad can show per hour */\n max_ad_frequency?: number;\n /** Minimum ad content rating */\n min_ad_rating?: \"G\" | \"PG\" | \"PG-13\" | \"R\";\n /** Preferred ad categories (prioritized in auctions) */\n preferred_categories?: Array<string>;\n};\n\nexport type ContentPreferencesResponse = {\n /** NOT EMITTED. No handler on this endpoint produces this key — neither `formatContentPreferences` nor the service feeding it has such a field. Advertiser blocking lives on the programmatic-settings resource instead, as `blocked_advertisers` on `GET/PUT /v1/partner/screens/{screenId}/programmatic-settings`. */\n blocked_advertisers?: Array<string>;\n /** The screen's own blocked list, `[]` when unset. Matched at approval time against the ad's typed `iab_categories` with `categoriesMatch`, so IAB codes and human-readable names both work. It is the only one of this schema's five list/rating fields the server actually emits. */\n blocked_categories?: Array<string>;\n /** NOT EMITTED, and the identifier does not occur anywhere in the API source. The only delivery cap this platform exposes to partners is the advisory `max_ads_per_hour` in the `/ads` response settings. */\n max_ad_frequency?: number;\n /** NOT EMITTED, and the identifier `min_ad_rating` does not occur anywhere in the API source. The response carries `max_age_rating` instead — an UPPER bound on the ad classification (`G`..`NC-17`), defaulting to `PG-13`, not a minimum. */\n min_ad_rating?: string;\n /** Always the literal `content_preferences`. This response is FLAT — there is no `success`/`data` envelope, unlike the L-bar and default-stream routes — and `POST .../content-preferences/apply-preset` returns this same object with an extra `preset_applied` field appended. */\n object?: string;\n /** NOT EMITTED, and the identifier does not occur anywhere in the API source. The nearest real control is `allowed_categories`, which the formatter does return and which acts as a scoring boost (+10 per match) rather than a hard preference. */\n preferred_categories?: Array<string>;\n /** Echo of the server-assigned `req.requestId`. The formatter attaches it only when one exists, so the key is ABSENT rather than null on a request that carried none. */\n request_id?: string;\n /** Echoed straight back from the `{screenId}` path parameter — the formatter is handed the path value, not a column — so it always equals what you asked for. */\n screen_id?: string;\n /** ISO-8601, but it is the SCREEN row's `updatedAt`, not a preferences-specific timestamp, so unrelated screen edits move it. When the screen has none the formatter substitutes the current time, so this field is never null and never proves a write landed. */\n updated_at?: string;\n};\n\nexport type ContentPreset = {\n /** Advertiser IDs blocked */\n blocked_advertisers?: Array<string>;\n /** Categories blocked by this preset */\n blocked_categories?: Array<string>;\n /** Preset description */\n description?: string;\n /** Unique preset identifier */\n id?: string;\n /** Minimum ad rating allowed */\n min_ad_rating?: \"G\" | \"PG\" | \"PG-13\" | \"R\";\n /** Human-readable preset name */\n name?: string;\n /** Full preference object applied by this preset */\n preferences?: Record<string, unknown>;\n};\n\nexport type CreatePartnerSensingRouteRequest = {\n /** The channel actions fired on a match; at least 1 and at most 10, each carrying exactly the destination its channel requires. A route with zero actions cannot exist, so use a `webhook` action for anything you do not want delivered to a person. */\n actions: Array<SensingRouteAction>;\n /** Seven-bit weekday mask evaluated in `activeTz`: bit 0 Monday through bit 6 Sunday, so the default 127 is every day and 0 means the route never fires. Requires `activeTz`. */\n activeDaysMask?: number;\n /** Inclusive end of the daily active window, minutes past local midnight in `activeTz` (0..1439). Set it BELOW `activeStartMinute` for an overnight window — 1200 to 360 is 20:00 through 06:00. */\n activeEndMinute?: number;\n /** Inclusive start of the daily active window, minutes past local midnight in `activeTz` (0..1439; 1200 = 20:00). Has no effect without `activeTz`, and omitting either end makes the window whole-day. */\n activeStartMinute?: number;\n /** IANA zone name (e.g. `America/New_York`) the active window is evaluated in, trimmed to 64 characters. Omitting it makes `activeStartMinute`, `activeEndMinute` and `activeDaysMask` inert — the route is then always active — and an unparseable zone fails open. */\n activeTz?: string;\n condition?: SensingRouteCondition;\n /** Minimum gap in seconds between alerts for the same (route, screen) episode; clamped to 0..86400, default 300. It also bounds the falling-edge clear window at 60 s, so a value below that re-arms the route faster too. */\n debounceSeconds?: number;\n /** Required when mode=digest. `field` is required for avg/max. */\n digest?: {\n field?: string;\n intervalMinutes?: number;\n metric?: \"count\" | \"avg\" | \"max\";\n windowMinutes?: number;\n };\n /** Only the JSON literal `false` creates the route disabled — any other value, including `0` and the string `\"false\"`, creates it enabled. Note the update endpoint inverts this test, so send a real boolean on both. */\n enabled?: boolean;\n /** `threshold` (the default) needs a `condition` and evaluates every incoming observation. `digest` needs a `digest` block instead, sends on a schedule, and rejects `screenFocusRegions` and explicit review timing. Only the exact string `digest` selects digest mode; anything else falls back to `threshold`. */\n mode?: \"threshold\" | \"digest\";\n /** Operator-supplied label, required, trimmed and capped at 200 characters. It is delivered as `route_name` on every fire and shown in operator surfaces; it is not the alert text. */\n name: string;\n /** Bind to a named Sense Anything profile (stable across redeploys). */\n profileName?: string;\n reviewTimingMode?: SensingRouteReviewTimingMode;\n reviewWaitSeconds?: SensingRouteReviewWaitSeconds;\n /** Optional per-camera rectangles or polygon lassos. Every entry must reference screenMongoIds. Supported only for visual threshold alerts using once_per_episode, on_escalation, or nag_until_resolved cadence; not digest, group-targeted, non-visual, or per_new_entity routes. */\n screenFocusRegions?: Array<SensingRouteScreenFocusRegion>;\n /** Screens to watch — all owned by this partner (foreign → 403). Provide EITHER screenMongoIds OR targetGroupId, not both. */\n screenMongoIds?: Array<string>;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainMinSamples?: number;\n /** How long the condition must hold before the FIRST alert, in seconds (600 = ten minutes). Omit to alert on the first true reading. Cadence-independent, because the evaluator compares timestamps - correct however often the screen reports, including when that rate changes while the condition holds. */\n sustainSeconds?: number;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainWindowSeconds?: number;\n /** A sg_ Screen Group id (owned by this partner) to target instead of an explicit screen set. The route fires on the group's live members. Requires SENSING_ROUTES_GROUP_TARGET_ENABLED. */\n targetGroupId?: string;\n};\n\n/**\n * Structured creative classification — the same shape the Trillboards venue portal renders for a served ad.\n */\nexport type CreativeClassification = {\n /** The name whoever FIRST submitted these bytes passed on `POST /creatives`, carried through the moderation queue. It is unverified free text, and because the review row is keyed on the content hash a later submitter of the same bytes reads the first submitter's value. `null` when none was supplied. */\n advertiser_name?: string | null;\n /** One of G, PG, PG-13, R, NC-17 (null until classified). */\n age_rating?: string | null;\n /** Descriptive output of the same classifier pass — visible subjects, keywords, scene setting, mood, a quality grade, and `text_content` recovered by OCR. Every field is `null`/empty until classification finishes. This is scene description, not the moderation verdict; that is `status` plus `moderation.flags`. */\n analysis?: {\n keywords?: Array<string>;\n mood?: string | null;\n quality?: string | null;\n setting?: string | null;\n subjects?: Array<string>;\n /** On-screen text (OCR). */\n text_content?: string | null;\n };\n /** Classifier output: `brand`, `category` and IAB category codes. All three are `null`/empty while `status` is `pending`, so an empty `iab_categories` means not yet classified rather than 'no categories apply'. */\n categorization?: {\n brand?: string | null;\n category?: string | null;\n iab_categories?: Array<string>;\n };\n /** Classification confidence, 0..1. */\n confidence?: number | null;\n /** SHA-256 of the bytes the SERVER fetched from your `media_url` — lowercase 64-hex, never a hash you declare — and `id` is this same value prefixed `sha256:`. Classification is content-addressed and global, so two partners submitting identical bytes share one record, and this is the byte-identity join key into `completed_impressions` and `vast_requests`. */\n content_sha256?: string;\n /** Canonical reference — `sha256:<hex>` of the creative bytes. Use as `creative_ref` when posting plays. */\n id?: string;\n /** `approved` is not an independent verdict — it is derived as `status === 'approved'`, so it is false for both `pending` and `blocked` and adds no information beyond `status`. `flags` is the classifier's reason list and is empty until classification finishes. */\n moderation?: {\n approved?: boolean;\n flags?: Array<string>;\n };\n /** Constant discriminator `creative_classification`. It is returned identically on the 202 (queued) and 200 (already classified) forms of this response, so it tells you nothing about whether classification has finished — read `status` for that. */\n object?: string;\n /** `pending` means queued and not yet classified. `approved`, `flagged` and `blocked` are terminal: re-submitting the same bytes then returns 200 immediately without re-running the classifier or consuming your daily classification quota, whereas a still-pending submit returns 202. The value is passed through from the shared review row unmapped, so a state outside this enum (e.g. `metadata_only`) can surface. */\n status?: \"pending\" | \"approved\" | \"flagged\" | \"blocked\";\n /** Always `null` in practice: the only writer of the underlying `creative_reviews.thumbnail_url` column has no remaining callers, so nothing populates it. When it did carry a value it was constrained to a Trillboards CDN host, so an off-host thumbnail was dropped rather than passed through. */\n thumbnail_url?: string | null;\n};\n\n/**\n * Native readiness hint delivered through existing partner SSE and the audited webhook queue. The hint is free; only the canonical reward page uses native paid admission.\n */\nexport type CreativeRewardsReadyEvent = {\n data: {\n advertisement_id: string;\n /** Lossless native build stamp. */\n build_stamp: string;\n builder_version: string;\n experiment_id: string;\n from: string;\n notification_id: string;\n object: \"creative_reward_ready\";\n recovery: \"rewind_and_dedupe\";\n request_idempotency_key_required: true;\n /** Canonical native reward path plus date scope; never contains credentials or a paid request key. */\n reward_reader_url: string;\n to: string;\n view: \"latest_state\";\n };\n event: \"creative.rewards_ready\";\n /** Audited webhook notification ID; SSE data carries the same ID inside data.notification_id. */\n id?: string;\n timestamp: string;\n};\n\nexport type DashboardResponse = {\n /** All amounts are integer US cents. `total_earned_cents` is lifetime earner revenue across every status (estimated + confirmed + paid); `available_payout_cents` is only the Net-60-matured payable balance minus anything already reserved, floored at 0; `next_payout_date` is simply the 1st of next month, computed in the handler with no reference to a scheduled transfer. `revenue_share_percent` is 0-100 and reads `60` when no `publisher_payout_rates` rule applies. */\n earnings?: {\n available_payout_cents?: number;\n next_payout_date?: string;\n pending_payout_cents?: number;\n revenue_share_percent?: number;\n total_earned_cents?: number;\n };\n /** Identity block copied off the partner row, where `created_at` is the registration timestamp. The row is read through a 10-minute Redis cache, so a just-changed name or status can lag by that much. */\n partner?: {\n created_at?: string;\n name?: string;\n slug?: string;\n status?: \"pending\" | \"active\" | \"suspended\" | \"terminated\";\n };\n /** The ten newest `impression_ledger` rows for the partner, newest first. `type` is always the literal `impression` — no other activity kind is ever emitted. */\n recent_activity?: Array<{\n ad_id?: string;\n screen_id?: string;\n timestamp?: string;\n type?: string;\n }>;\n /** `active_screens`/`offline_screens` count `online_status` `online`/`offline` from the live heartbeat mirror — a screen that has never heartbeated reads `unknown` and lands in NEITHER bucket, so the two need not sum to `total_screens`. `pending_screens` is the separate approval status and overlaps both. The three impression totals are deduped, GIVT-passed `completed_impressions` over midnight-today / the last 7 days / month-to-date, all cut in the API server's local timezone rather than the screen's. */\n stats?: {\n active_screens?: number;\n offline_screens?: number;\n pending_screens?: number;\n total_impressions_month?: number;\n total_impressions_today?: number;\n total_impressions_week?: number;\n total_screens?: number;\n };\n /** `connect_account_id` is **redacted**: it is the literal string `***` when an account is connected and `null` when it is not, so it can never be used as an identifier. `account_status` defaults to `pending` and must be `active` before a payout request is accepted. */\n stripe_status?: {\n account_status?: string;\n connect_account_id?: string;\n };\n /** The five screens with the most completed impressions since midnight today (server timezone). `name` falls back to the literal `Unknown Screen`, and `location` is a `City, State` string or `null` when either part is missing. */\n top_screens?: Array<{\n impressions_today?: number;\n location?: string;\n name?: string;\n screen_id?: string;\n }>;\n};\n\nexport type DataApi_AdSightingRecord = {\n /** The words read on the ad. */\n ad_text: string | null;\n advertiser_as_read: string | null;\n board_id: string | null;\n brand_as_read: string | null;\n brand_id: string | null;\n brand_name: string | null;\n cab_heading_deg: number | null;\n cab_lat: number | null;\n cab_lon: number | null;\n capture_mode: string | null;\n confidence: number | null;\n face_lat: number | null;\n face_lon: number | null;\n flight_end: string | null;\n flight_label_kind: string | null;\n flight_open: boolean | null;\n flight_passes: number | null;\n flight_reads: number | null;\n flight_start: string | null;\n /** The ad box in the frame, fractions of the frame. */\n frame_box_x0: number | null;\n frame_box_x1: number | null;\n frame_box_y0: number | null;\n frame_box_y1: number | null;\n frame_expires_at: string | null;\n /** A short-lived capability link to the whole still the ad was read off, face-blurred: the still's blurred rendition, never the original. Null until the still is blurred, and for a still the face-blur engine excluded. */\n frame_url: string | null;\n /** ssr-v2 or ssr-v1 (the still reader), vlmtext-v1 or edge_ocr_v1. */\n instrument_version: string | null;\n /** The face carried no advertiser. */\n is_vacancy: boolean;\n legibility: string | null;\n /** Legible, Partial or Illegible. */\n legibility_label: string | null;\n market_code: string | null;\n market_country: string | null;\n market_key: string | null;\n market_name: string | null;\n match_basis: string | null;\n match_distance_m: number | null;\n /** matched | unmatched | no_candidate. */\n match_status: string | null;\n orientation_idx: number | null;\n parent_name: string | null;\n /** The ad (on a close-up, its words) inside the photo, fractions of the photo. */\n photo_box_x0: number | null;\n photo_box_x1: number | null;\n photo_box_y0: number | null;\n photo_box_y1: number | null;\n photo_expires_at: string | null;\n /** The ad itself, face-blurred: closeup = the capture-resolution close-up a tablet uploaded, its blurred rendition whole; crop = the ad cut at request time from the blurred frame (its box padded 50% per side), never stored. Null when no picture of the ad can be shared. */\n photo_kind: \"closeup\" | \"crop\" | null | null;\n /** A short-lived capability link to the photo on our endpoint. It serves the face-blurred rendition only (faces are always blurred in any in-vehicle picture), never the original; null until the still is blurred, and for a still the face-blur engine excluded. */\n photo_url: string | null;\n /** What kind of placement the reader judged the ad to be (still reader ssr-v2): paid_ad = a third party's ad on space a media owner sells; own_premises = the business's own sign at or by its own place, or its own vehicle livery; other = neither. Null where the reading classified none (ssr-v1, vlmtext-v1, edge_ocr_v1). */\n placement: \"paid_ad\" | \"own_premises\" | \"other\" | null | null;\n product_as_read: string | null;\n publisher_name: string | null;\n /** The still instant, UTC. */\n seen_at: string;\n sighted_creative_id: string | null;\n /** Stable id of the sighting (32 hex). */\n sighting_id: string;\n source: string | null;\n surface: string | null;\n /** The surface as a buyer reads it (Billboard, Digital board, Storefront, ...). */\n surface_label: string | null;\n ticker: string | null;\n vehicle_side: string | null;\n};\n\nexport type DataApi_AdSightingsHistoryResponse = {\n data: {\n next_cursor: string | null;\n /** One row per month, market and brand (creative_brand_history): flights active and started, creatives launched, board faces and points, share of voice over every ad flight in the market (registered faces and unregistered points), and the market vacancy, each with its counts. */\n rows: Array<Record<string, unknown>>;\n };\n meta: {\n metric: \"creative_brand_history\";\n product: \"ad_sightings\";\n records: number;\n request: Record<string, unknown>;\n view: \"history\";\n };\n success: true;\n};\n\nexport type DataApi_AdSightingsResponse = {\n data: {\n next_cursor: string | null;\n rows: Array<DataApi_AdSightingRecord>;\n };\n meta: {\n product: \"ad_sightings\";\n records: number;\n request: Record<string, unknown>;\n };\n success: true;\n};\n\nexport type DataApi_AggregateResponse = {\n aggregations: Array<Record<string, unknown>>;\n group_by: string;\n metric: string;\n time_window: string;\n};\n\nexport type DataApi_Error = {\n error: {\n /** Stable machine-readable error code. Switch on this, never on `message`. The 143 CATALOGUED codes are published as the `ErrorCode` schema and served live by `GET /v1/errors`, which is the authority. This field is NOT limited to them: services emit further codes directly, and `ApiError.notFound(resource)` mints `<resource>_not_found` from an arbitrary resource name. Always handle an unrecognised code by falling back to `type`. */\n code: string;\n /** Per-field violations (populated from Zod errors). */\n details?: Array<{\n code?: string;\n message?: string;\n param?: string;\n }>;\n /** Link to the documentation for this error code (https://api.trillboards.com/docs/errors#<code>). */\n doc_url?: string;\n /** Agent-facing hints and suggestions. */\n help?: {\n portal_url?: string;\n suggestions?: Array<string>;\n summary?: string;\n try_next?: string;\n };\n /** Human-readable error message. Safe to surface to end-users. */\n message: string;\n /** Offending parameter name (when applicable). */\n param?: string;\n /** Echoed X-Request-Id for correlation. */\n request_id: string;\n /** Stripe-style error taxonomy (derived from utils/errorCatalog). */\n type: \"api_error\" | \"authentication_error\" | \"idempotency_error\" | \"invalid_request_error\" | \"permission_error\" | \"rate_limit_error\";\n };\n};\n\n/**\n * The 143 CATALOGUED error codes, generated from utils/errorCatalog.js — the same catalog that backs https://api.trillboards.com/docs/errors. This is the documented registry, NOT the complete set of codes the API can emit: a response `code` is an open string (see `TrillboardsError`). Use this list to recognise a code, never to assume a response cannot carry another.\n */\nexport type DataApi_ErrorCode = \"AUTH_REQUIRED\" | \"BRAND_CONFIGURATION_FAILED\" | \"BRAND_PROFILE_CONFLICT\" | \"BRAND_PROFILE_NOT_FOUND\" | \"CREATIVE_ADVERTISEMENT_NOT_OWNED\" | \"CREATIVE_MODERATION_UNAVAILABLE\" | \"CREATIVE_NOT_LINKED\" | \"CREATIVE_NOT_READY\" | \"CREATIVE_POLICY_CONFLICT\" | \"CREATIVE_TOO_LARGE\" | \"EXPERIMENT_COMPLETED\" | \"EXPERIMENT_NOT_FOUND\" | \"FEEDBACK_POLICY_REQUIRED\" | \"IDEMPOTENCY_CONFLICT\" | \"INVALID_BRAND_RULES\" | \"INVALID_CREATIVE_URL\" | \"INVALID_CREATIVE_WEIGHTS\" | \"INVALID_FEEDBACK_CREATIVE\" | \"INVALID_FEEDBACK_OBJECTIVE\" | \"INVALID_FEEDBACK_REQUEST\" | \"INVALID_POLICY_VERSION\" | \"INVALID_VARIANTS\" | \"MEDIA_BUY_NOT_FOUND\" | \"OPERATION_ALREADY_COMMITTED\" | \"OPERATION_IN_PROGRESS\" | \"POLICY_NOT_AVAILABLE\" | \"SOURCE_CREATIVE_NOT_FOUND\" | \"UNSUPPORTED_CREATIVE_TYPE\" | \"VARIANT_IDENTITY_CONFLICT\" | \"ad_interval_out_of_range\" | \"allocation_out_of_range\" | \"analytics_unavailable\" | \"api_key_expired\" | \"api_key_owner_missing\" | \"api_key_revoked\" | \"array_items_must_be_strings\" | \"array_too_large\" | \"auth_error\" | \"authentication_required\" | \"batch_size_exceeded\" | \"blocked_advertisers_too_large\" | \"blocked_iab_categories_too_large\" | \"client_ip_required\" | \"concurrent_request_limit\" | \"conflict\" | \"conflicting_lists\" | \"creative_policy_conflict\" | \"creative_policy_forbidden\" | \"daily_quota_exceeded\" | \"database_error\" | \"dataset_not_scopable\" | \"device_already_registered\" | \"device_identity_missing\" | \"device_identity_underivable\" | \"device_not_found\" | \"device_offline\" | \"dsp_access_denied\" | \"error\" | \"field_must_be_array\" | \"field_must_be_boolean\" | \"field_too_long\" | \"floor_price_out_of_range\" | \"forbidden\" | \"idempotency_error\" | \"idempotency_key_in_use\" | \"idempotency_key_mismatch\" | \"insufficient_data\" | \"insufficient_permissions\" | \"insufficient_scope\" | \"internal_error\" | \"invalid_ad_surface_mode\" | \"invalid_age_rating\" | \"invalid_api_key\" | \"invalid_api_key_format\" | \"invalid_array_items\" | \"invalid_creative_policy\" | \"invalid_currency\" | \"invalid_dataset\" | \"invalid_device_id\" | \"invalid_dimensions\" | \"invalid_display_mode\" | \"invalid_field_type\" | \"invalid_field_value\" | \"invalid_fingerprint\" | \"invalid_json\" | \"invalid_lbar_position\" | \"invalid_lbar_settings\" | \"invalid_measures\" | \"invalid_media_url\" | \"invalid_order_by\" | \"invalid_orientation\" | \"invalid_pip_position\" | \"invalid_pip_settings\" | \"invalid_preference_level\" | \"invalid_preferences\" | \"invalid_provider\" | \"invalid_screen_id\" | \"invalid_segment_rules\" | \"invalid_sizing_mode\" | \"invalid_stream_type\" | \"invalid_webhook_events\" | \"invalid_webhook_url\" | \"lbar_settings_must_be_object\" | \"lbar_size_out_of_range\" | \"missing_required_field\" | \"not_found\" | \"openrtb_auth_failed\" | \"openrtb_config_missing\" | \"paired_training_insert_failed\" | \"paired_training_rate_limit_exceeded\" | \"partner_inactive\" | \"pip_margin_out_of_range\" | \"pip_settings_must_be_object\" | \"pip_size_ratio_out_of_range\" | \"preferences_must_be_object\" | \"preferences_required\" | \"preset_id_required\" | \"preset_not_found\" | \"rate_limit_exceeded\" | \"resource_locked\" | \"screen_ids_required\" | \"screen_not_found\" | \"screen_not_in_scope\" | \"sdk_enrich_rate_limit_exceeded\" | \"sdk_impression_rate_limit_exceeded\" | \"sdk_qr_beacon_rate_limit_exceeded\" | \"sdk_qr_scan_rate_limit_exceeded\" | \"segment_limit_exceeded\" | \"segment_not_found\" | \"service_unavailable\" | \"stale_data\" | \"stream_content_required\" | \"stream_not_found\" | \"tier_insufficient\" | \"timeout\" | \"timeout_out_of_range\" | \"unknown_agent_type\" | \"unsupported_media_type\" | \"validation_error\" | \"webhook_limit_exceeded\" | \"webhook_not_found\" | \"webhook_secret_expired\" | \"webhook_url_unreachable\";\n\nexport type DataApi_ExampleHealthcheckResponse = {\n /** Echoed X-Request-Id for correlation. */\n request_id?: string;\n service: string;\n /** Always \"ok\" when the service is reachable. */\n status: \"ok\";\n version?: string;\n};\n\nexport type DataApi_FeedbackRewardQuery = {\n /** Opaque next_cursor from this same date scope; rewind after scan_complete. */\n cursor?: string;\n /** Inclusive first play date, YYYY-MM-DD; date range is at most 31 days. */\n from: string;\n /** Unique caller-owned billing key for this page; committed retries return the receipt, not another read. */\n idempotency_key: string;\n /** Maximum native play states returned on this metered page. */\n limit?: number;\n /** Inclusive last play date, YYYY-MM-DD; date range is at most 31 days. */\n to: string;\n};\n\nexport type DataApi_LookalikeResponse = {\n matches: Array<Record<string, unknown>>;\n matches_count: number;\n source_screen_id: string;\n};\n\nexport type DataApi_QueryDslBody = {\n /** Dataset name. Defaults to `inference_outcomes`. */\n dataset?: string;\n dimensions?: Array<string>;\n filters?: Array<DataApi_QueryFilter>;\n limit?: number;\n measures?: Array<string>;\n order_by?: Array<DataApi_QueryOrderBy>;\n};\n\nexport type DataApi_QueryDslResponse = {\n count: number;\n /** `observation_stream` when it is served from ClickHouse: the population the answer counts and the first instant the dataset holds. */\n coverage?: {\n population: string;\n since: string;\n };\n dataset: string;\n dimensions: Array<string>;\n /** `inference_outcomes` only. The `inference_source` values the query read (`null` when a range filter makes the set unstateable), and whether the camera-only default was applied because the request did not filter on `inference_source`. */\n inference_source?: {\n defaulted: boolean;\n values: Array<string> | null;\n };\n limit: number;\n measures: Array<string>;\n /** `inference_outcomes` only. `measured` when every row read is a camera measurement, `modeled` when every row is a venue-profile estimate, `mixed` when the answer aggregates both. */\n provenance?: \"measured\" | \"modeled\" | \"mixed\";\n /** Result rows. For `inference_outcomes` every row also carries `provenance` (`measured`, `modeled` or `mixed`). */\n rows: Array<Record<string, unknown>>;\n};\n\nexport type DataApi_QueryFilter = {\n field: string;\n op?: \"eq\" | \"in\" | \"gte\" | \"lte\" | \"between\";\n value?: unknown;\n};\n\nexport type DataApi_QueryOrderBy = {\n direction?: \"asc\" | \"desc\" | \"ASC\" | \"DESC\";\n field: string;\n};\n\nexport type DataApi_ScreenAudienceResponse = {\n audience: Record<string, unknown> | null;\n message?: string;\n screen_id: string;\n source?: string;\n};\n\nexport type DataApi_ScreenHistoryResponse = {\n count: number;\n metrics: Array<Record<string, unknown>>;\n screen_id: string;\n source?: \"camera\" | \"signal_derived\";\n};\n\nexport type DataApi_ScreensListResponse = {\n count: number;\n has_more: boolean;\n limit: number;\n next_offset: number | null;\n offset: number;\n screens: Array<Record<string, unknown>>;\n total: number;\n};\n\nexport type DataApi_SemanticSearchResponse = {\n query: string;\n results: Array<Record<string, unknown>>;\n results_count: number;\n time_window?: string;\n};\n\nexport type DataApi_StatsResponse = {\n active_screens: number;\n embeddings: Record<string, unknown>;\n metrics_last_24h: number;\n partner?: string;\n};\n\nexport type DataApi_StreamResponse = {\n /** Server-Sent Events stream. Each event carries `data: <json>` lines per the SSE spec. */\n note?: string;\n};\n\nexport type DataApi_UsageResponse = {\n data: Array<Record<string, unknown>>;\n period_days: number;\n};\n\n/**\n * Per-dataset record counts + date ranges for the partner's screens.\n * Returned by `GET /data-export/summary`.\n *\n */\nexport type DataExportSummaryResponse = {\n /** `lookback_days` is `min(?days, 400)` and defaults to 7. `screen_count` is how many screens the export is SCOPED to, not how many hold data. Each `datasets` entry is a `total_records`/`earliest`/`latest` triple, and a dataset whose count query FAILED is reported as `0` with null bounds rather than as an error. */\n data?: {\n /** Map of dataset name to record summary */\n datasets?: Record<string, {\n earliest?: string | null;\n latest?: string | null;\n total_records?: number;\n }>;\n lookback_days?: number;\n screen_count?: number;\n };\n /** Always `true`; a failure is raised to the global error handler and returns a canonical error envelope at a 4xx/5xx status. Note this response carries **no `request_id`**, unlike the sibling audience endpoints. */\n success?: boolean;\n};\n\n/**\n * Canonical INT-PK identity table for demand-side partners (DSPs/SSPs that Trillboards routes ad requests to). Wave 2 of pristine-state refactor. Replaces partner_key TEXT joins across the billing path.\n */\nexport type DemandPartners = {\n archived: boolean;\n created_at: string;\n /** demand_partner_id INT is the canonical FK going forward; partner_key TEXT is kept as the wire-format alternate-key FOREVER (external partner-API auth + admin UI rely on it). Never propose dropping ... */\n demand_partner_id?: string | null;\n display_name: string;\n partner_key: string;\n partner_type: string;\n /** public_alias UNIQUE: this is the obfuscated external alias partner-API consumers see. partnerAlias.js will read from this table after Wave 2 cutover. */\n public_alias: string;\n updated_at: string;\n};\n\nexport type DemographicsResponse = {\n /** `demographics.age_distribution` and `gender_split` are **hardcoded zeros even on the success path** — nothing computes them. `income_profile` and `group_composition` are shares in 0-1, not percentages, and `lifestyle_segments` is emitted as `{segment, percentage}` objects rather than the plain strings declared here. `data_quality.confidence` is a step function of sample size (>=100 gives 0.9, >=50 gives 0.7, >=20 gives 0.5, >=5 gives 0.3, else 0.1), not a statistical confidence, and `sample_size` counts measurement WINDOWS, not people. `period` echoes `?period=`; an unrecognised value silently falls back to 7 days while still being echoed verbatim. */\n data?: {\n data_quality?: {\n confidence?: number;\n sample_size?: number;\n };\n demographics?: {\n age_distribution?: {\n \"18-25\"?: number;\n \"26-35\"?: number;\n \"36-50\"?: number;\n \"50+\"?: number;\n };\n gender_split?: {\n female?: number;\n male?: number;\n };\n group_composition?: {\n couples?: number;\n families?: number;\n friends?: number;\n solo?: number;\n work_groups?: number;\n };\n income_profile?: {\n high?: number;\n low?: number;\n medium?: number;\n premium?: number;\n };\n lifestyle_segments?: Array<string>;\n };\n period?: string;\n screen_id?: string;\n screen_name?: string;\n timestamp?: string;\n };\n /** Per-request identifier (`req_` plus 32 hex), also returned on the `X-Request-Id` response header. */\n request_id?: string;\n /** Always `true` on a 2xx; failures return the error envelope (`success: false` plus an `error` object) at a 4xx/5xx status. */\n success?: boolean;\n};\n\nexport type Device = {\n /** Creation time of the DEVICE row (`earner_screen_devices`), not of the linked screen, which may be created later in the same request or on a subsequent re-registration. */\n created_at?: string;\n /** Trillboards-minted 24-char Mongo ObjectId hex for the `earner_screen_devices` row (`mongo_earnerscreen_device_id`). This is NOT the `device_id` you sent at registration — that comes back as `external_device_id`; the batch endpoint calls this same value `trillboard_id`. All three of `device_id`, `fingerprint` and `external_device_id` are accepted as the `{deviceId}` path parameter, resolved in that order. */\n device_id?: string;\n /** Saved partner category, independent of advertising classification. POST with an existing device_id and PATCH accept category updates. Legacy devices without a saved declaration retain their previous numeric-derived label until explicitly updated. */\n device_type?: string;\n /** Screen display specification. `width`/`height` are pixel resolution; `width_in`/`height_in` are physical inches. Pixel-only inputs are NOT recorded as physical size — pass `width_in`/`height_in` (most accurate) or `ppi` so the server can derive inches from pixels. */\n display?: {\n /** Display resolution height in pixels */\n height?: number;\n /** Physical screen height in inches */\n height_in?: number;\n orientation?: string;\n /** Pixels per inch (required to derive physical size from width/height) */\n ppi?: number;\n /** Display resolution width in pixels */\n width?: number;\n /** Physical screen width in inches */\n width_in?: number;\n };\n /** Static player URL of the form `https://screen.trillboards.com?fp=<fingerprint>`. It is deterministic and permanent — not a signed or expiring link — so it never rotates and anyone holding it can load the player. `null` until a screen is linked. */\n embed_url?: string;\n /** Venue-enrichment state. `status` is `complete` only when a classification source exists AND `location` actually resolved (coordinates or a city), so it never reports complete over placeholder geo; a partner-supplied venue category stamps the source as `partner` at registration, which means `complete` does not imply AI classification ran. `confidence` is dead — `venue_classification_confidence` has no column and no writer anywhere in the API, so it is always undefined and omitted from the JSON. */\n enrichment?: Record<string, unknown>;\n /** Verbatim echo of the `device_id` you sent in the registration body (`earner_screen_devices.partner_external_id`). It is the idempotency key scoped to your partner account: re-POSTing the same value updates the existing device instead of creating a second one. */\n external_device_id?: string;\n /** Deterministic device key: `P_` followed by the first 24 hex chars of `md5(\"<your partner id>_<external_device_id>\")`. It never rotates — the same partner plus the same `device_id` always regenerates it — and it is both the heartbeat/player routing key and the token embedded in `embed_url`. */\n fingerprint?: string;\n /** Saved publisher-declared audience impressions per ad play; defaults to 1. Update via PATCH /v1/partner/device/{deviceId}/impression-multiplier. */\n impression_multiplier?: number;\n /** Resolved geo, read from the enriched screen row first and falling back to the raw registration-time device row, with placeholder strings (\"City\", \"Vending Machine Location\") filtered out. `lat`/`lng` coalesce as a PAIR — a half-set or null-island (0,0) screen pair is discarded whole in favour of the device's coordinates — and `venue_type` here is the OpenOOH CHILD slug, while `venue.category` carries the parent. `timezone` is the one exception to screen-first: it is read device-first. */\n location?: Record<string, unknown>;\n location_capability?: PartnerLocationCapability;\n /** Stripe-style readiness, the same contract registration returns (see https://api.trillboards.com/docs/monetization-requirements). */\n monetization_status?: \"ready\" | \"limited\" | \"blocked\";\n /** The linked screen's name, falling back to the raw `external_device_id` when the screen has none. Omitting `name` at registration seeds the screen as `Partner Device: <device_id>`, so that literal string is what you get back rather than a null. */\n name?: string;\n requirements?: PartnerMonetizationRequirements;\n /** The linked screen's Mongo ObjectId hex (`earner_screens.mongo_earnerscreen_id`), minted server-side at registration. This — not `device_id` — is the id the non-device partner endpoints take (display settings, screen groups, fleet commands, analytics). Absent when no screen is linked to the device. */\n screen_id?: string;\n /** Provisioning state of the underlying screen row, not a liveness signal — a device that has stopped reporting still reads `approved`. Partner registration stamps `approved` on create (`partnerDeviceRegistrationService.js`), which is what almost every live partner device returns; `pending` is the formatter's fallback when the screen row carries no status of its own. */\n status?: \"pending\" | \"approved\" | \"active\" | \"offline\" | \"suspended\";\n /** `earner_screen_devices.updated_at`, which the device-mirror write buffer stamps `NOW()` on every flush — including heartbeat telemetry. It therefore tracks device liveness, not configuration changes; do not poll it to detect that a registration or PATCH landed. */\n updated_at?: string;\n /** OpenOOH classification of the screen, rebuilt from the typed `screen_venue_taxonomy` row. `category` is the PARENT slug and `subcategory` the child, with `openooh_venue_type_id` the unambiguous numeric child id — so a granular input like `hotel` reads back as a parent/child pair, not as what you sent. Empty until classification lands, unless your registration itself carried a resolvable `venue.category`/`subcategory`. */\n venue?: Record<string, unknown>;\n};\n\n/**\n * The device-authentication layer's refusal (missing, invalid or replayed device credential).\n */\nexport type DeviceAuthRefusal = {\n /** The HTTP status. */\n code?: number;\n /** device_token_required, invalid_device_token, device_not_found, device_identity_mismatch, device_token_subject_mismatch, device_screen_mismatch, missing_device_timestamp, missing_device_nonce, stale_device_request, replayed_device_request or replay_guard_unavailable. */\n error?: string;\n message?: string;\n status?: false;\n};\n\nexport type DeviceCapabilityPayload = {\n /** Runtime family of the agent (`fire-tv`, `android-tv`, `tablet`, `tablet-lite`, `webos`, `vidaa`, `web-browser`, `brightsign`). It is NOT read from here on the partner path — the server derives `agent_type` from `telemetry.agentType` plus hardware signals, and overrides a self-declared `web-browser` when the model/UA say otherwise, because partner SDKs running in a WebView historically reported `web-browser` for physical DOOH tablets. */\n agentType?: string | null;\n /** Agent build version. Do not use it to identify an artifact: three different Android build constants populate this one field across three unrelated numbering schemes, so the same string can mean different code. It is also not read from here on the partner path — the version the server keeps comes from the body `agent_version`/`sdk_version`, and only for heartbeats that self-identify as Trillboards SDKs. */\n agentVersion?: string | null;\n /** Camera/microphone sensing state. On the partner path ONLY the nested `cameraHealth` block is persisted (to the typed `camera_health_*` columns) — `cameraAvailable`, `cameraType`, `cameraCount`, `microphoneAvailable` and `sensingMode` reach no column on this path. The presence of `cameraHealth` is also an in-band signal that the heartbeat came from a Trillboards SDK, which is what allows `agent_version` to be stored. `cameraAvailable` self-devolves to false after a camera has been failed for over 24 h. */\n audienceSensing?: {\n cameraAvailable?: boolean | null;\n cameraCount?: number | null;\n cameraHealth?: {\n lastFailureClass?: string | null;\n lastFailureMessage?: string | null;\n lastFrameAtMs?: number | null;\n retryCount?: number | null;\n state?: string | null;\n [key: string]: unknown;\n } | null;\n cameraType?: string | null;\n microphoneAvailable?: boolean | null;\n sensingMode?: string | null;\n [key: string]: unknown;\n } | null;\n /** Declares volume and mute control. Hardcoded `true`/`true` on the Android agents — it is not probed against `AudioManager`, so it is a claim about the agent, not proof the hardware obeys. Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power. */\n audioControl?: {\n mute?: boolean | null;\n volume?: boolean | null;\n [key: string]: unknown;\n } | null;\n /** Schema version of this capability block, persisted to `earner_screen_devices.capabilities_version` for drift attribution. Every shipped Android and Vega agent emits the constant `1` and nothing overrides it, so it carries no information today — but the server treats it as an arbitrary integer, not a bounded enum. Note the server only records it when an `mlCapabilities` block is present or when it is sent with no `mlCapabilities` at all. */\n capabilitiesVersion?: number | null;\n /** Declares an ambient-light and a temperature sensor. Only `ambientLight` is a real probe (`SensorManager.getDefaultSensor(TYPE_LIGHT) != null`, false on any throw); `temperature` is hardcoded `false` on the Android agents, and the actual temperature reading that does ship comes from the battery sensor via telemetry, not from here. Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power. */\n environmentSensors?: {\n ambientLight?: boolean | null;\n temperature?: boolean | null;\n [key: string]: unknown;\n } | null;\n /** Declares HDMI input switching and app switching. Also hardcoded per agent build rather than probed (a tablet reports `hdmi: false`, `appSwitch: true`). Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power. */\n inputControl?: {\n appSwitch?: boolean | null;\n hdmi?: boolean | null;\n [key: string]: unknown;\n } | null;\n /** Whether the agent holds Android Device Owner (`DevicePolicyManager.isDeviceOwnerApp`) — the privilege that gates reboot, kiosk lock, APK install and wipe. Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power, precisely so a partner cannot flip it and unlock those commands. */\n isDeviceOwner?: boolean | null;\n /** Declares remote restart/reboot/screenshot/cache-clear/kiosk/APK-install/wipe. On the first-party Android agents four of the seven are simply `isDeviceOwner` repeated, and the partner SDK reports the whole block `false` because \"partner hosts own their device management\". Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power. */\n managementCapabilities?: {\n clearCache?: boolean | null;\n installApk?: boolean | null;\n kioskMode?: boolean | null;\n reboot?: boolean | null;\n restart?: boolean | null;\n screenshot?: boolean | null;\n wipe?: boolean | null;\n [key: string]: unknown;\n } | null;\n /** The hardware manifest that decides which on-device ML models a screen is offered, and one of the few capability blocks the partner path DOES persist. Two traps: every nullable integer must be sent as JSON `null`, never 0 — the server treats 0 as a probe-failure sentinel and drops it so the COALESCE upsert preserves the prior value — and `screenWidthPx`/`screenHeightPx` below 100 are rejected outright, after agents reporting inches corrupted 940+ rows. A `recommendedModelTier` of `UNKNOWN` (or NULL) excludes the device from on-device demographics entirely. */\n mlCapabilities?: {\n availableRamMb?: number | null;\n chipsetName?: string | null;\n chipsetVendor?: string | null;\n cpuAbi?: string | null;\n densityDpi?: number | null;\n gpuDelegateSupported?: boolean | null;\n gpuName?: string | null;\n hasNpu?: boolean | null;\n maxVlmSizeMb?: number | null;\n nnapiSupported?: boolean | null;\n npuName?: string | null;\n osApiLevel?: number | null;\n recommendedModelTier?: string | null;\n screenHeightPx?: number | null;\n screenWidthPx?: number | null;\n totalRamMb?: number | null;\n [key: string]: unknown;\n } | null;\n /** Declares HDMI-CEC, wake-on-LAN and soft-blackout support. On the Android agents these are HARDCODED per build, not probed (a tablet always reports `cec: false`, `wol: false`, `softBlackout: true`), so they describe the agent variant rather than the panel. Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power. */\n powerControl?: {\n cec?: boolean | null;\n softBlackout?: boolean | null;\n wol?: boolean | null;\n [key: string]: unknown;\n } | null;\n /** Counts ESP32-S3 Wi-Fi CSI sensor nodes discovered on the LAN — it is a statement about the venue's network, not about this device's own radio (`csiHardwareType` is the literal `\"esp32-s3\"` when any node is found, `\"none\"` otherwise). Parsed by the shared capability builder but then DISCARDED on the partner heartbeat path: it is not in the partner column whitelist, which is a deliberate security boundary — partner SDKs must not be able to drive device management or power; it is also the one CSI field the ingest schema deliberately keeps, since it is a startup capability counter rather than a streaming data field. */\n wifiCsiSensing?: {\n csiAvailable?: boolean | null;\n csiHardwareType?: string | null;\n csiNodeCount?: number | null;\n [key: string]: unknown;\n } | null;\n [key: string]: unknown;\n};\n\nexport type DeviceSummary = {\n /** Creation time of the DEVICE row, not of the linked screen (which can be created later). */\n created_at?: string;\n /** Trillboards-minted 24-char Mongo ObjectId hex for the `earner_screen_devices` row (`mongo_earnerscreen_device_id`). Not the `device_id` you sent at registration — that is returned as `external_device_id`. Accepted as the `{deviceId}` path parameter, as are `fingerprint` and `external_device_id`. */\n device_id?: string;\n /** Saved mutable partner category, independent of advertising classification. Legacy devices without a saved declaration retain their previous numeric-derived label until explicitly updated. */\n device_type?: string;\n /** Emits `orientation` (from the screen, defaulting to `landscape`) and `ppi` (from the device row). `width`/`height` still read `dimension_width`/`dimension_height`, columns DROPPED from `earner_screen_devices` by migration `20260527180000`, so they resolve to undefined and are omitted from the JSON entirely — registered pixels now live in `screen_width_px`/`screen_height_px`. Physical inches you send as `width_in`/`height_in` are stored but never echoed here. */\n display?: Record<string, unknown>;\n /** Static, unsigned player URL `https://screen.trillboards.com?fp=<fingerprint>`. It never expires or rotates, and is `null` until a screen is linked. */\n embed_url?: string;\n /** Venue-enrichment state. `status` reads `complete` only when a classification source exists AND `location` actually resolved, and a partner-declared venue category alone sets that source — so `complete` does not mean AI classification ran. `confidence` is dead: `venue_classification_confidence` has no column and no writer in the API, so it is always omitted. */\n enrichment?: Record<string, unknown>;\n /** Verbatim echo of the `device_id` you sent in the registration body (`partner_external_id`). It is the per-partner idempotency key: re-registering the same value updates the existing device rather than creating another. */\n external_device_id?: string;\n /** Deterministic device key: `P_` plus the first 24 hex chars of `md5(\"<your partner id>_<external_device_id>\")`. It never rotates, and it is the token embedded in `embed_url` as well as the heartbeat routing key. */\n fingerprint?: string;\n /** Resolved geo: the enriched screen row first, falling back to the registration-time device row, with placeholder strings coalesced away. `lat`/`lng` coalesce as a pair, `venue_type` is the OpenOOH child slug, and `timezone` alone is read device-first. */\n location?: Record<string, unknown>;\n location_capability?: PartnerLocationCapability;\n /** Stripe-style readiness, the same contract registration returns (see https://api.trillboards.com/docs/monetization-requirements). */\n monetization_status?: \"ready\" | \"limited\" | \"blocked\";\n /** The linked screen's name, falling back to the raw `external_device_id` when the screen has none. A registration that omits `name` seeds the screen as `Partner Device: <device_id>`. */\n name?: string;\n requirements?: PartnerMonetizationRequirements;\n /** The linked screen's Mongo ObjectId hex (`earner_screens.mongo_earnerscreen_id`). This is the id every non-device partner endpoint takes — display settings, screen groups, fleet commands, analytics. Absent when no screen is linked. */\n screen_id?: string;\n /** The linked screen's `earner_screens.status` verbatim. Partner registration auto-approves, so the live value is `approved` (`deactivated` after a DELETE, `pending` only while no screen is linked) — not the `pending`/`active`/`offline`/`suspended` vocabulary the `Device` schema declares, whose mapper has no production call site. */\n status?: string;\n /** `earner_screen_devices.updated_at`, stamped `NOW()` by the device-mirror write buffer on every flush including heartbeats — a liveness signal, not a configuration-change marker. */\n updated_at?: string;\n /** OpenOOH classification rebuilt from the typed `screen_venue_taxonomy` row: `category` is the PARENT slug, `subcategory` the child, `openooh_venue_type_id` the numeric child id. Empty until classification lands, unless registration supplied a resolvable venue category. */\n venue?: Record<string, unknown>;\n};\n\nexport type DisplaySettingsInput = {\n /** Default ad duration in seconds */\n ad_duration_default?: number;\n /** Screen brightness percentage */\n brightness?: number;\n content_fit?: ContentFitSettings;\n /** Ad display mode */\n display_mode?: \"fullscreen\" | \"l-bar\" | \"split\";\n /** What to show when no ads */\n idle_content?: \"default_stream\" | \"black\" | \"logo\";\n /** Mute audio */\n mute?: boolean;\n /** Screen orientation */\n orientation?: \"landscape\" | \"portrait\" | \"auto\";\n /** QR code position on screen */\n qr_code_position?: \"bottom-left\" | \"bottom-right\" | \"top-left\" | \"top-right\" | \"none\";\n /** Transition effect between ads */\n transition_effect?: \"fade\" | \"slide\" | \"none\";\n /** Audio volume percentage */\n volume?: number;\n};\n\nexport type DisplaySettingsResponse = {\n /** NOT EMITTED and NOT WRITABLE — the identifier does not occur anywhere in the API source. The nearest real controls are `ad_interval_seconds` on this resource (rotation cadence, 30-300s) and the slot duration the playback policy derives (clamped 5-60s). */\n ad_duration_default?: number;\n /** NOT EMITTED by this endpoint. `buildAdSurfaces` is computed on a display-settings write, but only to populate the `display_settings_updated` socket event pushed to the device — the HTTP body is the formatter's output and contains no `ad_surfaces`. The declared item shape is wrong too: real surfaces carry `id`, `surface_type`, `enabled`, `display_area_ratio`, `dimensions` and `allowed_ad_types`, never `position`/`width_percent`/`height_percent`. The L-bar PUT is the one HTTP response that does return them. */\n ad_surfaces?: Array<{\n height_percent?: number;\n id?: string;\n position?: string;\n width_percent?: number;\n }>;\n /** NOT EMITTED and NOT WRITABLE. The writer's ten-key allow-list has no `brightness`, so a PUT silently drops it, and the formatter never returns one. Brightness exists in this product only as inbound device telemetry on the heartbeat, which this resource cannot set. */\n brightness?: number;\n content_fit?: ContentFitSettings;\n /** Real and emitted: `fullscreen` (default), `l-bar` or `pip`, read from the typed `display_mode` column in preference to the legacy `display_preferences` JSONB. Note the writer validates a THREE-value enum including `pip`, while the L-bar resource's own `display_mode` accepts only `fullscreen`/`l-bar`. */\n display_mode?: string;\n /** NOT EMITTED and NOT WRITABLE — the identifier does not occur anywhere in the API source. What actually plays when no ad is available is the screen's default streams, managed through the default-streams routes. */\n idle_content?: string;\n /** NOT EMITTED and NOT WRITABLE. Use `sound_enabled: false` instead — that key the formatter does return, defaulting to `true`. */\n mute?: boolean;\n /** Always `display_settings`. The response is FLAT — no `success`/`data` envelope — and it comes from exactly one formatter shared by the GET, PUT and PATCH, so all three return an identical shape. */\n object?: string;\n /** NOT EMITTED under this name — the formatter returns `screen_orientation` instead (`landscape`, `portrait`, `mixed` or `unknown`, defaulting to `landscape`). The writer's allow-list also only accepts `screen_orientation`, so an `orientation` key in a PUT body is silently dropped. */\n orientation?: string;\n /** NOT EMITTED by this endpoint, for the same reason as `ad_surfaces` — it is computed on a write only to fill the device socket event. The declared `max_duration_seconds`/`min_break_between_ads`/`allow_skip` keys do not exist in the real policy either; `buildPlaybackPolicy` produces `policy_version`, millisecond intervals, `max_paid_share`, `surface_policy`, `schedule_policy`, `pip_settings` and `lbar_settings`. */\n playback_policy?: {\n allow_skip?: boolean;\n max_duration_seconds?: number;\n min_break_between_ads?: number;\n };\n /** NOT EMITTED and NOT WRITABLE — the identifier does not occur anywhere in the API source. */\n qr_code_position?: string;\n /** Echo of the server-assigned `req.requestId`, attached only when one exists — the key is absent, not null, otherwise. */\n request_id?: string;\n /** Echoed from the `{screenId}` path parameter rather than read from a column. */\n screen_id?: string;\n /** NOT EMITTED and NOT WRITABLE — the identifier `transition_effect` does not occur anywhere in the API source. There is no transition control on this resource. */\n transition_effect?: string;\n /** ISO-8601 of the SCREEN row's `updatedAt`, not a display-settings-specific timestamp; when the screen has none the formatter substitutes the current time, so it is never null and never proves a write landed. */\n updated_at?: string;\n /** NOT EMITTED and NOT WRITABLE — the string `volume` does not appear in the display-settings service or the formatter at all. The only audio control on this resource is the boolean `sound_enabled`. */\n volume?: number;\n};\n\n/**\n * User-owned ad screens. Each row is a logical screen that can display ads. Links user to device. PG primary with Mongo legacy mirror.\n */\nexport type EarnerScreens = {\n ad_interval_seconds?: number | null;\n ads_disabled_reasons: Array<string>;\n advertisement_sizing?: string | null;\n archetype_photo_key?: string | null;\n archetype_photo_synced_at?: string | null;\n archetype_photo_url?: string | null;\n auction_cohort?: string | null;\n auction_enabled_at?: string | null;\n auction_mode?: string | null;\n auction_type?: string | null;\n avg_dwell_time_seconds?: number | null;\n back_online_alert_sent_at?: string | null;\n billing_mode: string;\n block_group_geoid?: string | null;\n blocked_advertisers?: Array<string> | null;\n blocked_iab_categories?: Array<string> | null;\n brand_substitution_consent_at?: string | null;\n brand_substitution_consent_by?: string | null;\n brand_substitution_mode: string;\n branding_overlay_enabled: boolean;\n branding_overlay_hide_on_ads: boolean;\n branding_overlay_logo_transformed_url: string;\n branding_overlay_logo_url: string;\n /** NUMERIC(5,4). */\n branding_overlay_opacity: number;\n /** NUMERIC(5,4). */\n branding_overlay_padding_ratio: number;\n branding_overlay_placement: string;\n branding_overlay_position: string;\n /** NUMERIC(5,4). */\n branding_overlay_size_ratio: number;\n cms_payment_status: string;\n commercial_value_score?: number | null;\n content_filtering_enabled?: boolean | null;\n /** deleted_at IS NULL required — soft-delete nulls earnerscreen_device_id for partial unique index */\n deleted_at?: string | null;\n delivery_mode: string;\n device_platform_override?: string | null;\n diagonal_inches?: number | null;\n display_mode?: string | null;\n distributor_id?: number | null;\n dma_code?: string | null;\n dma_name?: string | null;\n dynamic_floor_pricing?: boolean | null;\n enrichment_attempts: number;\n enrichment_failed_at?: string | null;\n environment_type?: string | null;\n /** NUMERIC(12,6). */\n floor_price_cpm?: number | null;\n foot_traffic_multiplier?: number | null;\n gam_ad_unit_code?: string | null;\n gam_ad_unit_id?: string | null;\n gam_ad_unit_path?: string | null;\n gam_sync_status?: string | null;\n gam_synced_at?: string | null;\n geocode_source?: string | null;\n geocoded_at?: string | null;\n geocoding_raw_response?: Record<string, unknown> | null;\n hb_allocation?: number | null;\n hb_enabled?: boolean | null;\n hb_force_request?: boolean | null;\n hb_last_sync_at?: string | null;\n hb_min_interval_seconds?: number | null;\n hb_provider?: string | null;\n hb_request_mode?: string | null;\n hb_timeout_ms?: number | null;\n hb_vast_tag_url?: string | null;\n height_inches?: number | null;\n hive_stack_screen_id?: string | null;\n hive_stack_site_id?: string | null;\n hive_stack_sync_status?: string | null;\n hive_stack_synced_at?: string | null;\n hive_stack_unit_id?: string | null;\n hive_stack_unit_uuid?: string | null;\n is_demo_data: boolean;\n is_standby: boolean;\n l_bar_ads_enabled?: boolean | null;\n l_bar_position?: string | null;\n last_enrichment_attempt_at?: string | null;\n last_offline_transition_at?: string | null;\n last_programmatic_sync_at?: string | null;\n latitude?: number | null;\n longitude?: number | null;\n loop_plan_id?: number | null;\n media_capability?: \"image_video\" | \"image_only\" | \"video_only\" | null;\n mediation_priority?: number | null;\n mobile_distance_threshold_m?: number | null;\n mobile_update_interval_s?: number | null;\n mobile_vehicle_type?: string | null;\n neighborhood?: string | null;\n ntp_sync_required: boolean;\n offline_alert_sent_at?: string | null;\n offline_push_alert_sent_at?: string | null;\n operating_hours?: Record<string, unknown> | null;\n partner_mongo_id?: string | null;\n peak_hours?: Array<string> | null;\n photo_flag_notes?: string | null;\n photo_flag_reason?: string | null;\n photo_flagged: boolean;\n photo_flagged_at?: string | null;\n photo_flagged_by_mongo_id?: string | null;\n photo_notification_count: number;\n photo_notification_sent_at?: string | null;\n photo_quality_sent_at?: string | null;\n photo_review_status?: string | null;\n photos?: Record<string, unknown> | null;\n physical_size_set_at?: string | null;\n physical_size_set_by?: string | null;\n place_name?: string | null;\n placement_type?: string | null;\n places_rating?: number | null;\n places_review_count?: number | null;\n pref_allow_religious?: boolean | null;\n pref_allow_tobacco?: boolean | null;\n pref_allowed_categories?: Array<string> | null;\n pref_allowed_keywords?: Array<string> | null;\n pref_allowed_subjects?: Array<string> | null;\n pref_auto_approve_known_brands?: boolean | null;\n pref_blocked_categories?: Array<string> | null;\n pref_blocked_keywords?: Array<string> | null;\n pref_blocked_subjects?: Array<string> | null;\n pref_level?: string | null;\n pref_max_age_rating?: string | null;\n preferred_deal_ids: Array<string>;\n prog_allocation_pct?: number | null;\n prog_currency: string;\n programmatic_enabled: boolean;\n region: string;\n registration_source?: string | null;\n screen_placement?: string | null;\n serving_tenant_id?: number | null;\n slot_duration_seconds?: number | null;\n sound_enabled?: boolean | null;\n ssp_enabled?: boolean | null;\n state_iso_3166_2?: string | null;\n sync_enabled?: boolean | null;\n sync_group_name?: string | null;\n sync_is_master?: boolean | null;\n sync_last_sync_at?: string | null;\n sync_master_screen_id?: string | null;\n taggify_screen_id?: string | null;\n taxonomy_migrated_at?: string | null;\n taxonomy_spec_version?: string | null;\n venue_category?: string | null;\n venue_child?: string | null;\n venue_child_id?: string | null;\n venue_city?: string | null;\n /** enrichment_status: PG GENERATED from (venue_classification_source, enrichment_failed_at); a real classification DOMINATES a stale failure stamp (success wins). Never written directly. Values: pendi... */\n venue_classification_source?: string | null;\n venue_grandchild?: string | null;\n venue_parent?: string | null;\n venue_state?: string | null;\n venue_subcategory?: string | null;\n waterfall_optimized_at?: string | null;\n /** waterfall_partners is the ONLY pin store (order + whitelist; screen_partner_settings was never built). Serving reads it only with a screen_header_bidding_config row and ignores it once waterfall_op... */\n waterfall_partners: Array<string>;\n waterfall_total_timeout_ms?: number | null;\n /** Physical width_inches/height_inches/diagonal_inches are inches. Pixels live on earner_screen_devices; the raw screen has no resolution_width/resolution_height columns. */\n width_inches?: number | null;\n};\n\nexport type EarningsBreakdownResponse = {\n /** Keyed by `date` for `group_by=day|week` and by `screen_id` (plus a `screen_name`) for `group_by=screen`; the unused key is absent rather than null. Week rows are labelled `<year>-W<ISO week>`. `earnings_cents` is allocated cumulatively so the rows sum exactly to `summary.total_cents`. */\n breakdown?: Array<{\n /** Date or screen_id depending on group_by */\n date?: string;\n earning_sources?: EarningsSources;\n earnings_cents?: number;\n impressions?: number;\n /** Screen ID (when grouped by screen) */\n screen_id?: string;\n }>;\n /** **Both fields are hardcoded** — `estimated_cents` is always `0` and `confirmation_date` always `null`. Read `EarningsResponse.pending_confirmation_cents` for the real Net-60 pending figure. */\n pending_programmatic?: {\n confirmation_date?: string | null;\n estimated_cents?: number;\n };\n /** `start`/`end` echo the request; with neither supplied the window is the last 30 days ending now. The end bound is EXCLUSIVE in the underlying query (`period_date < end`), so passing today's date omits today. */\n period?: {\n end?: string;\n group_by?: \"day\" | \"week\" | \"screen\";\n start?: string;\n };\n /** Integer US cents for the selected period. With source reporting enabled, direct_cents is verified first-party revenue and programmatic_cents is the remainder; earning_sources supplies the business classification. Disabled reporting retains the legacy split. */\n summary?: {\n direct_cents?: number;\n earning_source_version?: 1;\n earning_sources?: EarningsSources;\n programmatic_cents?: number;\n total_cents?: number;\n total_impressions?: number;\n };\n};\n\n/**\n * Partner earnings summary with optional daily series\n * (`@trillboards/api-client` `EarningsData`).\n *\n */\nexport type EarningsData = {\n /** **Never emitted.** No partner earnings endpoint computes a CPM; the earnings chart returns impressions and cents only, so a caller must divide for itself. */\n average_cpm: number;\n /** Optional per-day earnings/impressions series. */\n daily?: Array<{\n /** Day label (YYYY-MM-DD). */\n date: string;\n earnings: number;\n impressions: number;\n }>;\n /** **Never emitted**, so its unit is undefined. Every money field the partner earnings endpoints actually return is integer US cents and carries a `_cents` suffix. */\n last_payout_amount?: number;\n /** **Never emitted.** No handler populates it — read `GET /v1/partner/earnings/transactions?type=payout`, whose rows carry `created_at` and `amount_cents`. */\n last_payout_date?: string;\n /** Balance not yet paid out. */\n pending_payout: number;\n /** Declared for the SDK type but **not emitted**: `GET /v1/partner/connect/earnings` returns `{summary, chart, period}` where `period` is an OBJECT (`days`/`start`/`end`), not this string. */\n period: string;\n /** **Not emitted under this name**, so its unit is undefined. The live earnings chart returns `summary.total_earnings_cents` — integer US cents — summed over the requested window from the payout-accounting rollup of `programmatic_earnings_daily`. */\n total_earnings: number;\n /** **Not emitted under this name.** The live earnings chart returns `summary.total_impressions`, the impression count carried on the same daily earner-revenue rows the cents are summed from. */\n total_impressions: number;\n};\n\nexport type EarningsResponse = {\n /** Amount available for immediate payout */\n available_for_payout_cents?: number;\n /** Current-month integer US cents. With source reporting enabled, direct_campaigns is verified first-party revenue and programmatic_google is the remaining aggregate, not Google-specific. programmatic_floodgates remains zero. Prefer earning_sources; disabled reporting retains the legacy all-programmatic split. */\n breakdown?: {\n direct_campaigns?: number;\n programmatic_floodgates?: number;\n programmatic_google?: number;\n };\n earning_source_version?: 1;\n earning_sources?: EarningsSources;\n /** `amount_cents` is integer US cents and simply repeats `available_for_payout_cents`; `date` is the 1st of next month, computed in the handler with no reference to any scheduled transfer; `stripe_status` is the partner's Connect account status, which must be `active` before a payout request is accepted. */\n next_payout_estimate?: {\n amount_cents?: number;\n date?: string;\n stripe_status?: string;\n };\n /** Minimum amount required for payout */\n payout_threshold_cents?: number;\n /** Programmatic earnings pending Net-60 confirmation */\n pending_confirmation_cents?: number;\n /** Partner's revenue share percentage */\n revenue_share_percent?: number;\n /** Estimated earnings for current month */\n this_month_cents?: number;\n /** Total earnings since partner started */\n total_lifetime_cents?: number;\n};\n\n/**\n * Optional reporting breakdown in integer US cents. Components sum to the existing amount. Missing evidence is Unclassified; absence of this object means reporting is unavailable.\n */\nexport type EarningsSources = {\n /** Paid first-party portal and offline IO bookings. */\n direct_bookings: number;\n /** Verified open-auction delivery outside a private deal. */\n open_exchange: number;\n /** Verified delivered DSP/SSP PMP, PG and preferred deals. */\n programmatic_deals: number;\n /** Earnings lacking supported allocation evidence. */\n unclassified: number;\n};\n\n/**\n * Legacy free-text error shape. COLLAPSED onto the shared `LegacyError`\n * definition so there is exactly one declaration of this shape across the\n * specs. Kept as a distinct NAME because hand-authored operations\n * reference it by that name; the shape is unchanged.\n *\n */\nexport type Error = LegacyError;\n\n/**\n * The 143 CATALOGUED error codes, generated from utils/errorCatalog.js — the same catalog that backs https://api.trillboards.com/docs/errors. This is the documented registry, NOT the complete set of codes the API can emit: a response `code` is an open string (see `TrillboardsError`). Use this list to recognise a code, never to assume a response cannot carry another.\n */\nexport type ErrorCode = \"AUTH_REQUIRED\" | \"BRAND_CONFIGURATION_FAILED\" | \"BRAND_PROFILE_CONFLICT\" | \"BRAND_PROFILE_NOT_FOUND\" | \"CREATIVE_ADVERTISEMENT_NOT_OWNED\" | \"CREATIVE_MODERATION_UNAVAILABLE\" | \"CREATIVE_NOT_LINKED\" | \"CREATIVE_NOT_READY\" | \"CREATIVE_POLICY_CONFLICT\" | \"CREATIVE_TOO_LARGE\" | \"EXPERIMENT_COMPLETED\" | \"EXPERIMENT_NOT_FOUND\" | \"FEEDBACK_POLICY_REQUIRED\" | \"IDEMPOTENCY_CONFLICT\" | \"INVALID_BRAND_RULES\" | \"INVALID_CREATIVE_URL\" | \"INVALID_CREATIVE_WEIGHTS\" | \"INVALID_FEEDBACK_CREATIVE\" | \"INVALID_FEEDBACK_OBJECTIVE\" | \"INVALID_FEEDBACK_REQUEST\" | \"INVALID_POLICY_VERSION\" | \"INVALID_VARIANTS\" | \"MEDIA_BUY_NOT_FOUND\" | \"OPERATION_ALREADY_COMMITTED\" | \"OPERATION_IN_PROGRESS\" | \"POLICY_NOT_AVAILABLE\" | \"SOURCE_CREATIVE_NOT_FOUND\" | \"UNSUPPORTED_CREATIVE_TYPE\" | \"VARIANT_IDENTITY_CONFLICT\" | \"ad_interval_out_of_range\" | \"allocation_out_of_range\" | \"analytics_unavailable\" | \"api_key_expired\" | \"api_key_owner_missing\" | \"api_key_revoked\" | \"array_items_must_be_strings\" | \"array_too_large\" | \"auth_error\" | \"authentication_required\" | \"batch_size_exceeded\" | \"blocked_advertisers_too_large\" | \"blocked_iab_categories_too_large\" | \"client_ip_required\" | \"concurrent_request_limit\" | \"conflict\" | \"conflicting_lists\" | \"creative_policy_conflict\" | \"creative_policy_forbidden\" | \"daily_quota_exceeded\" | \"database_error\" | \"dataset_not_scopable\" | \"device_already_registered\" | \"device_identity_missing\" | \"device_identity_underivable\" | \"device_not_found\" | \"device_offline\" | \"dsp_access_denied\" | \"error\" | \"field_must_be_array\" | \"field_must_be_boolean\" | \"field_too_long\" | \"floor_price_out_of_range\" | \"forbidden\" | \"idempotency_error\" | \"idempotency_key_in_use\" | \"idempotency_key_mismatch\" | \"insufficient_data\" | \"insufficient_permissions\" | \"insufficient_scope\" | \"internal_error\" | \"invalid_ad_surface_mode\" | \"invalid_age_rating\" | \"invalid_api_key\" | \"invalid_api_key_format\" | \"invalid_array_items\" | \"invalid_creative_policy\" | \"invalid_currency\" | \"invalid_dataset\" | \"invalid_device_id\" | \"invalid_dimensions\" | \"invalid_display_mode\" | \"invalid_field_type\" | \"invalid_field_value\" | \"invalid_fingerprint\" | \"invalid_json\" | \"invalid_lbar_position\" | \"invalid_lbar_settings\" | \"invalid_measures\" | \"invalid_media_url\" | \"invalid_order_by\" | \"invalid_orientation\" | \"invalid_pip_position\" | \"invalid_pip_settings\" | \"invalid_preference_level\" | \"invalid_preferences\" | \"invalid_provider\" | \"invalid_screen_id\" | \"invalid_segment_rules\" | \"invalid_sizing_mode\" | \"invalid_stream_type\" | \"invalid_webhook_events\" | \"invalid_webhook_url\" | \"lbar_settings_must_be_object\" | \"lbar_size_out_of_range\" | \"missing_required_field\" | \"not_found\" | \"openrtb_auth_failed\" | \"openrtb_config_missing\" | \"paired_training_insert_failed\" | \"paired_training_rate_limit_exceeded\" | \"partner_inactive\" | \"pip_margin_out_of_range\" | \"pip_settings_must_be_object\" | \"pip_size_ratio_out_of_range\" | \"preferences_must_be_object\" | \"preferences_required\" | \"preset_id_required\" | \"preset_not_found\" | \"rate_limit_exceeded\" | \"resource_locked\" | \"screen_ids_required\" | \"screen_not_found\" | \"screen_not_in_scope\" | \"sdk_enrich_rate_limit_exceeded\" | \"sdk_impression_rate_limit_exceeded\" | \"sdk_qr_beacon_rate_limit_exceeded\" | \"sdk_qr_scan_rate_limit_exceeded\" | \"segment_limit_exceeded\" | \"segment_not_found\" | \"service_unavailable\" | \"stale_data\" | \"stream_content_required\" | \"stream_not_found\" | \"tier_insufficient\" | \"timeout\" | \"timeout_out_of_range\" | \"unknown_agent_type\" | \"unsupported_media_type\" | \"validation_error\" | \"webhook_limit_exceeded\" | \"webhook_not_found\" | \"webhook_secret_expired\" | \"webhook_url_unreachable\";\n\nexport type ErrorCodeResponse = {\n code: string;\n description: string;\n doc_url: string;\n /** Example canonical error envelope for this code. */\n example_response: {\n error: {\n code: string;\n doc_url: string;\n message: string;\n param?: unknown;\n type: string;\n };\n };\n http_status: number;\n /** Always \"error_code\". */\n object: \"error_code\";\n type: string;\n};\n\n/**\n * A single partner API error-log record\n * (`@trillboards/api-client` `ErrorEntry`) — one logged error occurrence\n * scoped to the partner. Distinct from the public error *catalog* entries\n * (`ErrorListItem` / `ErrorCodeResponse`), which describe error codes in\n * the abstract.\n *\n */\nexport type ErrorEntry = {\n /** Stable machine error code (e.g. not_found). */\n code: string;\n /** Derived mechanically as `https://api.trillboards.com/docs/errors#<code>`, so it is always well-formed whether or not that anchor exists. It appears on live error envelopes and catalog entries; no per-partner error-log endpoint returns it. */\n doc_url: string;\n /** The API path that produced the error. */\n endpoint: string;\n /** **No producer.** `GET /v1/errors` returns the static error CATALOG (code definitions in the abstract), not per-partner occurrences, and the per-request metadata the API does retain is keyed by `request_id` and carries no `id`. */\n id: string;\n /** **Never emitted as part of a logged occurrence.** Human-readable text appears only on a live error envelope or a catalog entry; the retained per-request record stores `error_code`/`error_type` and no message. */\n message: string;\n /** The HTTP method of the failing request. */\n method: string;\n /** The request parameter the error concerns, if any. */\n param?: string;\n /** The `req_`-prefixed identifier of the failing request, minted server-side and returned both on the `X-Request-Id` header and inside every error envelope. It is the only handle that links a caller-side failure to server-side request metadata. */\n request_id: string;\n /** **Not emitted by any error endpoint.** The per-request metadata the API retains does stamp an ISO timestamp at response time, but it lives in a bounded in-memory LRU cache, not a queryable error log. */\n timestamp: string;\n /** Stripe-style error taxonomy (matches the SDK's ApiErrorType). */\n type: \"invalid_request_error\" | \"authentication_error\" | \"permission_error\" | \"rate_limit_error\" | \"idempotency_error\" | \"api_error\";\n};\n\n/**\n * The complete set of error envelopes this API may return. Documented on operations whose surface emits more than one — see `x-superseded-by` on each member for the migration target. New endpoints return `TrillboardsError`; switch on `error.code`, never on a message string.\n */\nexport type ErrorEnvelope = TrillboardsError | HybridError | LegacyCodedError | LegacyError;\n\nexport type ErrorListItem = {\n code: string;\n description: string;\n doc_url: string;\n http_status: number;\n type: string;\n};\n\nexport type ErrorListResponse = {\n data: Array<ErrorListItem>;\n /** Always \"list\". */\n object: \"list\";\n /** Total number of error codes. */\n total: number;\n};\n\n/**\n * Stripe-style error taxonomy. Derived from the distinct `type` values in utils/errorCatalog.js (6 values).\n */\nexport type ErrorType = \"api_error\" | \"authentication_error\" | \"idempotency_error\" | \"invalid_request_error\" | \"permission_error\" | \"rate_limit_error\";\n\n/**\n * Exact DSP-matchable audience identity kept separate from the resettable\n * OS advertising ID. `hem_sha256` is an existing SHA-256 digest: send it\n * directly (the server lowercases hexadecimal once and never hashes it\n * again). `uid2_token` is an opaque, case-sensitive UID2 advertising\n * token: send it exactly as issued and include `expires_at` so it can be\n * refreshed; tokens contain no whitespace. Use `unavailable` with no\n * `id` to clear a prior value.\n *\n */\nexport type ExtendedIdentity = {\n /** OpenRTB person-based identifier type. */\n atype: 3;\n /** When the token stops being usable — for a UID2 token this is the refresh deadline, and it is stored verbatim regardless of `status` (unlike the advertising-identity envelope, which discards `expires_at` on an `available` state). `null` means no declared expiry, not \"already expired\". */\n expires_at?: string | null;\n /** The identity value itself, up to 4096 characters and containing no whitespace. Omit it (or send `null`) with `unavailable` to CLEAR a previously stored identity — that is the only way to retract one; sending the envelope with `available` and an empty id is rejected instead. */\n id?: string | null;\n /** `hem_sha256` must be exactly 64 hexadecimal characters and is lowercased ONCE server-side and never re-hashed — send the digest, not the email. `uid2_token` is an opaque case-sensitive token forwarded byte-for-byte with only a whitespace check, so do not trim, lowercase or re-encode it. Together with `source` this pair is the row key: one identity per `(id_type, source)` per device. */\n id_type: \"hem_sha256\" | \"uid2_token\";\n /** When the device observed this identity — and it is the CONFLICT-RESOLUTION CLOCK, not just provenance: the upsert applies only when this timestamp is greater than or equal to the one already stored, so a replayed or out-of-order envelope is silently discarded with no error. An unparseable value rejects the whole array. */\n observed_at: string;\n /** Canonical top-level domain for this identity system. */\n source: string;\n /** Version of the SDK or integration that produced this identity, up to 128 characters, kept for provenance only — no server logic branches on it. It is trimmed, and a whitespace-only string is stored as `null` rather than as an empty string. */\n source_version?: string | null;\n /** `available` requires `id`; `unavailable` requires `id` to be ABSENT — sending both is a validation failure, and because normalisation is all-or-nothing across the array that single bad element discards every other identity in the same request while the heartbeat still returns 200. */\n status: \"available\" | \"unavailable\";\n};\n\nexport type FeedbackBrand = {\n advertisement_id: string;\n brand_name: string;\n profile_id: string;\n reactive_enabled: true;\n stance: \"adversarial\" | \"complementary\" | \"same_brand\" | \"intent_capture\";\n};\n\nexport type FeedbackBrandRequest = {\n /** Native brand reference assets used by creative generation. */\n brand_assets?: Array<Record<string, unknown>>;\n /** Optional brand intent supplied to the native creative generator. */\n brand_intent_text?: string | null;\n /** Advertiser brand name for the experiment’s native campaign. */\n brand_name: string;\n /** Native brand colours, typography, voice, products and required or prohibited elements. */\n brand_rules: {\n /** The advertiser brand name, e.g. \"Nike\". */\n brand_name: string;\n /** Elements that MUST appear in every creative, e.g. [\"logo top-right\",\"legal disclaimer\"]; may be empty. */\n mandatory_elements: Array<string>;\n /** Brand colour palette. */\n palette: {\n /** Accent colours; may be empty. */\n accents: Array<string>;\n /** Primary brand colour (hex or named); empty string if unknown. */\n primary: string;\n /** Secondary brand colour; empty string if none. */\n secondary: string;\n };\n /** Key products to feature; may be empty. */\n products: Array<{\n /** Short product description; empty string if none. */\n description: string;\n /** Product name. */\n name: string;\n }>;\n /** Elements that must NEVER appear, e.g. [\"competitor logos\",\"alcohol\"]; may be empty. */\n prohibited_elements: Array<string>;\n /** Preferred reactive stances for this brand; may be empty. */\n stance_preferences: Array<\"adversarial\" | \"complementary\" | \"same_brand\" | \"intent_capture\">;\n /** Approved taglines / slogans; may be empty. */\n taglines: Array<string>;\n /** Typography guidance. */\n typography: {\n /** Body typeface / style guidance; empty string if none. */\n body: string;\n /** Headline typeface / style guidance; empty string if none. */\n headline: string;\n };\n /** Voice + tone adjectives, e.g. [\"confident\",\"playful\"]; may be empty. */\n voice_tone: Array<string>;\n };\n /** Optional existing brand profile owned by this advertiser and advertisement. */\n profile_id?: string;\n /** Native reactive relationship used to generate or stage this creative. */\n stance?: \"adversarial\" | \"complementary\" | \"same_brand\" | \"intent_capture\";\n};\n\nexport type FeedbackBrandResponse = {\n data: FeedbackBrand;\n success: true;\n};\n\nexport type FeedbackCreateRequest = {\n /** Exact decimal policy version; zero enrolls an unclaimed native policy. */\n expected_policy_version?: string;\n /** Caller-owned key for this exact operation; do not reuse for changed inputs. */\n idempotency_key: string;\n /** Your funded native AdCP media buy ID. */\n media_buy_id: string;\n /** Name for this funded creative feedback experiment. */\n name: string;\n /** Measured objective: gaze share 0–1, gaze overlap milliseconds, or capped mean dwell milliseconds. */\n objective: \"gaze_share\" | \"gaze_overlap_ms\" | \"dwell_ms\";\n /** Native package ID within that media buy. */\n package_id: string;\n};\n\nexport type FeedbackCreativeReview = {\n success: boolean;\n};\n\nexport type FeedbackExperiment = {\n advertisement_id: string;\n advertiser_id: string;\n id: string;\n media_buy_id: string;\n name: string;\n objective: \"gaze_share\" | \"gaze_overlap_ms\" | \"dwell_ms\";\n package_id: string;\n policy_mode: \"attribution\" | \"experiment\" | \"external_agent\";\n policy_version: string;\n status: string;\n variants: Array<FeedbackVariant>;\n};\n\nexport type FeedbackExperimentDetail = FeedbackExperiment & {\n creatives: Array<FeedbackRecoveredCreative>;\n creatives_truncated: boolean;\n };\n\nexport type FeedbackExperimentDetailResponse = {\n data: FeedbackExperimentDetail;\n success: true;\n};\n\nexport type FeedbackExperimentResponse = {\n data: FeedbackExperiment;\n success: true;\n};\n\nexport type FeedbackGenerateRequest = {\n /** Image aspect ratio, such as 16:9, matched to your booked screens. */\n aspectRatio?: string;\n /** Creative direction added to your configured native brand rules. */\n brief?: string;\n /** Unique caller-owned generation key; committed retries expose creativeId and renderKey in the receipt. */\n idempotency_key: string;\n /** Native reactive relationship used to generate or stage this creative. */\n stance?: \"adversarial\" | \"complementary\" | \"same_brand\" | \"intent_capture\";\n};\n\nexport type FeedbackGenerateResponse = {\n data: FeedbackGeneratedCreative;\n success: true;\n};\n\nexport type FeedbackGeneratedCreative = {\n brandReviewStatus: string;\n cdnUrl: string;\n creativeId: string;\n deduped: boolean;\n headline?: string | null;\n moderationStatus?: string;\n renderKey: string;\n status?: string;\n success: true;\n usage_receipt: Record<string, unknown>;\n};\n\n/**\n * Exactly one native image source is required. source_creative_id resolves only within the authenticated partner. Inline bytes require content_type and are validated against the actual image MIME and byte hash by native staging.\n */\nexport type FeedbackImportRequest = {\n /** Public HTTPS image URL; provide exactly one image source. */\n asset_url?: string;\n /** Actual image MIME; required for inline bytes and checked against decoded content. */\n content_type?: \"image/png\" | \"image/jpeg\" | \"image/jpg\" | \"image/webp\";\n /** Optional headline stored with the native imported creative. */\n headline?: string;\n /** Unique caller-owned import key; a committed retry returns the existing usage receipt. */\n idempotency_key: string;\n /** Base64 PNG, JPEG or WebP bytes; provide exactly one source and content_type. */\n image_base64?: string;\n /** Existing creative ID owned by the authenticated partner; provide exactly one image source. */\n source_creative_id?: string;\n /** Native reactive relationship used to generate or stage this creative. */\n stance?: \"adversarial\" | \"complementary\" | \"same_brand\" | \"intent_capture\";\n};\n\nexport type FeedbackPolicyRequest = {\n /** Exact decimal policy version from the latest experiment; stale updates return 409. */\n expected_version: string;\n /** Replacement weights for registered variants; omitted variants are inactive. */\n weights: Array<{\n /** Registered native creative ID to assign this weight. */\n creative_id: string;\n /** Weight between zero and one; zero disables selection. Eligible weights normalize at serving. */\n weight: number;\n }>;\n};\n\nexport type FeedbackRecoveredCreative = {\n advertisement_mongo_id: string | null;\n brand_profile_id: string | null;\n brand_review_status: string;\n cdn_url: string;\n content_sha256: string | null;\n created_at: string;\n creative_id: string;\n headline: string | null;\n linked_to_experiment_ad: boolean;\n moderation_status: string | null;\n render_key: string | null;\n reviewed_at: string | null;\n stance: string | null;\n};\n\nexport type FeedbackRegisterVariantsRequest = {\n /** Exact decimal policy version from the latest experiment; stale updates return 409. */\n expected_version: string;\n /** Approved native variants; identity and bytes remain immutable. */\n variants: Array<{\n /** Approved native generated or imported creative ID. */\n creative_id: string;\n /** Your model version associated with this immutable variant. */\n model_version?: string;\n /** Stable model variant identity within this experiment. */\n variant_id: string;\n }>;\n};\n\nexport type FeedbackReviewRequest = {\n /** Owned native creative ID awaiting your explicit brand review. */\n creative_id: string;\n /** Approve or reject brand use; global AI moderation remains a separate gate. */\n decision: \"approve\" | \"reject\";\n};\n\nexport type FeedbackReviewResponse = {\n data: FeedbackCreativeReview;\n success: true;\n};\n\nexport type FeedbackRewardQuery = {\n /** Opaque next_cursor from this same date scope; rewind after scan_complete. */\n cursor?: string;\n /** Inclusive first play date, YYYY-MM-DD; date range is at most 31 days. */\n from: string;\n /** Unique caller-owned billing key for this page; committed retries return the receipt, not another read. */\n idempotency_key: string;\n /** Maximum native play states returned on this metered page. */\n limit?: number;\n /** Inclusive last play date, YYYY-MM-DD; date range is at most 31 days. */\n to: string;\n};\n\nexport type FeedbackRewardRow = {\n action: \"upsert\" | \"retract\";\n ad_id: string | null;\n build_stamp: string;\n builder_version: string;\n /** Four ordered strings: play_date, screen_mongo_id, play_source, play_key. */\n canonical_key: Array<string>;\n content_sha256: string;\n /** Lossless issued v1 JSON snapshot; empty string when unissued. Contains variant_id and creative_policy_version. */\n creative_feedback: string;\n creative_id: string | null;\n dwell_capped_ms_mean: number | null;\n dwell_overlap_ms: number | null;\n dwell_windows_n: number | null;\n gaze_face_rows_n: number | null;\n gaze_overlap_ms: number | null;\n /** Measured gaze share, 0–1; NULL when not measured. */\n gaze_pct_on_screen: number | null;\n gaze_seconds: number | null;\n gaze_windows_n: number | null;\n play_completed_at: string | null;\n play_date: string;\n play_key: string;\n play_source: string;\n play_started_at: string | null;\n request_id: string | null;\n reward_revision_id: string;\n screen_mongo_id: string;\n superseded: number;\n};\n\nexport type FeedbackRewards = {\n experiment_id: string;\n next_cursor: string | null;\n objective: \"gaze_share\" | \"gaze_overlap_ms\" | \"dwell_ms\";\n rewind_next_poll: boolean;\n rows: Array<FeedbackRewardRow>;\n scan_complete: boolean;\n usage_receipt: Record<string, unknown>;\n view: \"latest_state\";\n};\n\nexport type FeedbackRewardsResponse = {\n data: FeedbackRewards;\n success: true;\n};\n\nexport type FeedbackStopRequest = {\n /** Exact decimal policy version from the latest experiment; stale updates return 409. */\n expected_version: string;\n};\n\nexport type FeedbackVariant = {\n content_sha256?: string | null;\n creative_id: string;\n model_version?: string | null;\n variant_id: string;\n};\n\n/**\n * A fleet-command job: one batch that fanned a device command across a screen group's\n * members. Poll via GET /fleet-commands/{id}.\n *\n */\nexport type FleetCommand = {\n /** The device command that was fanned out — one of `reload`, `update_settings`, `restart`, `screenshot`, `clear_cache`, `update_content`, `refresh_ads`, `update_config`, `get_state`, `show_ad`, `play_ad`. Validated in the application against `ALLOWED_COMMANDS`, with no database constraint, so the allow-list can grow without a schema migration. */\n command_type?: string;\n /** Set on BOTH terminal states — `completed` and `failed` — so it is not a success marker. `null` only while the job has not reached a terminal state. */\n completed_at?: string | null;\n /** When the job row was inserted, i.e. when the batch was accepted. Because execution is synchronous, `created_at` and `completed_at` bracket the entire fan-out. */\n created_at?: string;\n /** The raw thrown message, set only when the JOB itself failed; `null` otherwise. Per-screen dispatch failures do NOT populate it — those live in `results[].error`, and a job whose targets all failed still reports `status: \"completed\"` with `failed` > 0. */\n error?: string | null;\n /** Screens that could not be enqueued */\n failed?: number;\n /** The `sg_<uuid>` screen group this batch fanned out to. It is a plain TEXT reference, not a foreign key: groups are only ever soft-deleted, so the job outlives its group and keeps the id as an audit record. */\n group_id?: string | null;\n /** Job id: the literal prefix `fc_` followed by a v4 UUID (`pgRepo/fleetCommands.js` mints `fc_${crypto.randomUUID()}`). Opaque — do not parse the UUID half. */\n id?: string;\n /** Constant string `fleet_command` — the Stripe-style type discriminator, present both here and inside the `data` of a `fleet_command.*` webhook. */\n object?: string;\n /** Screens the command was enqueued to */\n queued?: number;\n /** One entry per screen the dispatch touched, persisted only when the fan-out actually produced rows. `results[].status` is `queued` or `failed` — the per-screen dispatch outcome, a different vocabulary from the job's own `status` — and `device_command_id` is the queued device-command's Mongo ObjectId, or `null` when nothing was queued (e.g. `error: \"No device linked\"`). On a `fleet_command.failed` webhook this is always `[]`, because the dispatch never completed. */\n results?: Array<{\n device_command_id?: string | null;\n error?: string | null;\n screen_id?: string;\n status?: \"queued\" | \"failed\";\n }>;\n /** Execution is synchronous inside the create request — the job walks `queued` to `running` to `completed` (or `failed`) before the POST returns — so a poll will in practice only ever observe a terminal value. `queued` and `running` exist for a future async worker that claims `queued` rows. */\n status?: \"queued\" | \"running\" | \"completed\" | \"failed\";\n /** Member screens resolved for this command */\n targeted?: number;\n /** Bumped on each job state transition (`queued` to `running` to terminal), so on a completed job it is effectively the moment the fan-out finished. */\n updated_at?: string;\n};\n\n/**\n * Webhook body for a group fleet-command terminal state (`fleet_command.completed` |\n * `fleet_command.failed`). Standard `{ id, event, timestamp, data }` envelope; `data` is the\n * `fleet_command` job object (status, targeted/queued/failed counts, per-screen results; on\n * failure, an `error` message). The event type is also in the `X-Trillboards-Event` header —\n * verify the compound `X-Trillboards-Signature` over the raw body first.\n *\n */\nexport type FleetCommandEvent = {\n /** The fleet_command job object. */\n data?: Record<string, unknown>;\n /** The terminal state that fired, also sent in the `X-Trillboards-Event` header and mirrored to the SSE stream. Note `fleet_command.completed` fires even when individual screens failed — check `data.failed` and `data.results[]`. Delivery is gated by the `SCREEN_GROUP_EVENTS_ENABLED` runtime flag, off by default. */\n event?: \"fleet_command.completed\" | \"fleet_command.failed\";\n /** `evt_<base36 epoch ms>_<12 hex>`, minted once per occurrence and reused across retries of that delivery — so it is the idempotency key. The same id also goes to every endpoint subscribed to the event, so dedupe per (endpoint, id). It is not the delivery audit id and is not queryable. */\n id?: string;\n /** Stamped when the server enqueued the delivery, not when it was sent; retries keep the original value. It is the emit time of the terminal-state event, which is not the same as the job's `completed_at`. */\n timestamp?: string;\n};\n\n/**\n * Programmatic header-bidding configuration and the resolved VAST waterfall for the screen. The `vast_waterfall.sources[]` array is the programmatic demand path — fetch each source's `vast_url` in priority order and hand the returned VAST to your IMA SDK. Source names are opaque public aliases. Source-health request-pressure governance does not remove SQL/Admin-enabled sources from this catalog.\n *\n */\nexport type HeaderBiddingSettings = {\n /** `null` unless the screen falls in the ad-buffering rollout bucket. `min_buffer`, `max_buffer` and `warmup_count` are COUNTS OF PREFETCHED ADS (defaults 3 / 5 / 5), not seconds or bytes; `max_cache_age_seconds` (default 300) is how long a prefetched VAST may be held before it must be discarded. */\n ad_buffering?: {\n enabled?: boolean;\n max_buffer?: number;\n max_cache_age_seconds?: number;\n min_buffer?: number;\n warmup_count?: number;\n } | null;\n /** `null` unless the screen falls in the ad-pod experiment bucket. When present it carries `enabled`, `pod_size` (how many ads to request in a single pod) and `variant_name` (the pod-size arm this screen hashed into) — the arm is assigned server-side, not chosen by the client. */\n ad_pod?: Record<string, unknown> | null;\n /** Share of ad slots the client should give to programmatic, as a decimal FRACTION 0-1 (`0.20` = 20%), defaulting to `1.0`. Same underlying value the CMS exposes as `programmatic_allocation_percent` (0-100, i.e. this times 100). Only honoured when `request_mode` is `allocation`; under `always` it is ignored. */\n allocation?: number;\n /** One id per waterfall BUILD (one `/ads` response), shared by every entry in `vast_waterfall.sources[]`; the narrower per-source key is `request_id`. This is the value that lands in `vast_requests.auction_request_id`, so it is what groups all the source attempts belonging to a single ad opportunity. Echo it, never mint your own. */\n auction_request_id?: string;\n /** Constant `true` whenever this object is present — the whole `header_bidding_settings` object is `null` for a screen that is not programmatically eligible. This is header-bidding *configuration*, not the serving master switch: that is `programmatic_enabled` (surfaced as `ProgrammaticSettingsResponse.enabled`) plus any active ads-disabled reason, and the legacy `enabled` shadow was deliberately removed from the serving decision. */\n enabled?: boolean;\n /** `+`-joined tags for the yield experiments this screen is bucketed into (e.g. `yield_buffer_treatment+ad_pod_pod_3`), covering ad buffering, fast loop and ad pods; `null` when none is active. Assignment is a consistent hash of the screen id, so it is stable for a screen across requests. Report it back for analytics — on its own it changes no client behaviour. */\n experiment_variant?: string | null;\n /** `null` unless the screen falls in the fast-loop experiment bucket. When present it carries `enabled` and `ad_to_ad_gap_ms` — the gap to leave between consecutive ads, in MILLISECONDS (default 5000), which is the experiment's shortened alternative to the normal ~30s cadence. */\n fast_loop?: Record<string, unknown> | null;\n /** No live producer — `null` on every response. The only assignment is guarded on a partner-level `fetchMode` setting, and no demand partner in the registry defines one, so this key is never populated at either the top level or per source. Do not branch on it. */\n fetch_mode?: string | null;\n /** Legacy predecessor of `request_mode`, retained only as the fallback that selects `always` (true) versus `allocation` (false) when no explicit `request_mode` is stored. An explicit `request_mode` always wins — do not read the two independently or you will disagree with the server on screens that set both. */\n force_request?: boolean;\n /** `null` except on screens in a revenue-recovery experiment arm, where it carries `name` and `reason` plus the IMA overrides to apply for that arm — some subset of `num_redirects`, `vast_load_timeout_ms`, `load_video_timeout_ms`, `source_timeout_ms`, `mime_types`, `enable_preloading`, `sdk_version_override` and `client_hints_override`. The key set is deliberately open: apply the keys you recognise and ignore the rest. */\n ima_profile?: Record<string, unknown> | null;\n /** Minimum gap the client should leave between VAST requests, in SECONDS (not milliseconds), default `30`, accepted range 0-300. Advisory: no server-side check enforces it — the `/ads` rate limiter is a separate mechanism that does not read this value. */\n min_interval_seconds?: number;\n /** Always `null`. Every stored value for this key is JSON `null`, it has no typed home on the screen record (dropped from the typed read path), and the builder emits `hbs.prebid || null` — there is no live producer. Prebid Server demand, when it participates, is spliced into `vast_waterfall.sources[]`, not surfaced here. */\n prebid?: Record<string, unknown> | null;\n /** Opaque public alias of the first waterfall source — the same string as `variant_name` and `source_name`, not the `floodgates`/`prebid`/`other` bidding-provider value the CMS accepts on `ProgrammaticSettingsInput.provider`. The key is absent entirely when the waterfall resolved no sources. */\n provider?: string;\n /** Player tuning selected purely from the screen's `country`; today only `IN` differs from the default — `timeout_ms` 25000 vs 12000, `fallback_timeout_ms` 35000 vs 18000, `vquality` 1 vs 2, `enable_network_adaptation` true vs false, and `ad_buffer_percentage` as a 0-1 fraction of network capacity (0.25 vs 1.0). Note the wire shapes: `preferred_resolution` is an object `{width, height}` and `vquality` an integer, not the strings declared here. */\n regional_config?: {\n ad_buffer_percentage?: number;\n enable_network_adaptation?: boolean;\n fallback_timeout_ms?: number;\n preferred_resolution?: string;\n timeout_ms?: number;\n vquality?: string;\n } | null;\n /** Per-request waterfall identifier; echo it back on programmatic-event reports. */\n request_id?: string;\n /** `allocation` = request only on the `allocation` share of slots; `always` = request on every slot and ignore `allocation`. These are the only two accepted values. Defaults to `always` on the static-tag path; on the waterfall path it defaults from the legacy `force_request` boolean (`always` when true, `allocation` when false). */\n request_mode?: string;\n /** Opaque alias of the source these top-level fields describe — the first waterfall source, identical to `provider`/`variant_name`. Absent when the waterfall resolved no sources. */\n source_name?: string | null;\n /** The first source's `priority` sort key, copied up to the top level. Same semantics as `VastWaterfallSource.priority`: an opaque ascending key that may start at 0 and may be non-contiguous — it is not an index, a count, or a position you can do arithmetic on. */\n source_priority?: number | null;\n /** Opaque public alias of the first waterfall source, byte-identical to `provider` and `source_name` on the same response. Format is `src_<6 hex>` (SHA-256 of the partner key) or `var_<6 hex>` (of the variant name), plus the fixed `src_test` for the sandbox test source and `inbound_dsp` for a collapsed DSP-seat source. Never a real partner name, and it may be rotated per tenant — echo it back on `programmatic-event` reports rather than persisting it as an identity. */\n variant_name?: string;\n /** Client-side VAST/creative caching directive, `null` unless the fleet-level VAST-cache switch is on (shipped default: off). `ttl_seconds` defaults to 21600 (6 hours). `priority` is a fixed rank of `1.5` — between paid ads (1) and self-promo (2) — and is emitted as a NUMBER despite the string type declared here. */\n vast_cache?: {\n enabled?: boolean;\n priority?: string;\n ttl_seconds?: number;\n } | null;\n /** Convenience copy of `vast_waterfall.sources[0].vast_url` — the server overwrites it with the first source on every build and drops the key entirely when the waterfall resolved no sources. It is not an extra tag, so fetching only this URL is exactly the `sources[0]`-and-stop defect the `VastWaterfall` contract warns about. */\n vast_tag_url?: string;\n vast_waterfall?: VastWaterfall;\n} | null;\n\nexport type HttpProbeResult = {\n /** IP address that was probed. The target list is PARASITIC on mDNS — the hosts discovered in the same cycle plus the Wi-Fi gateway, de-duplicated and capped at 10. There is no subnet scan, so a device that advertises nothing on mDNS is never probed. */\n host: string;\n /** Always `80` — the probe only ever tries plain HTTP on port 80, and the value is a literal at the call site rather than something the scanner chose. Printer ports 515 and 9100-9102 are refused before any socket opens, because an HTTP request to a JetDirect/LPD port makes the printer physically spool the bytes. */\n port?: number | null;\n /** The HTTP `Server:` response header from a `HEAD /` request (HTTP/1.0, 1.5 s timeout). Never null in an emitted row — a probe that gets no usable header produces no row, and the value is rejected outright if it is over 256 characters or contains an `@`, angle brackets, control characters, or the words `password`/`passwd`/`secret`/`phpinfo`. Failures are counted separately in `skipReasonCounts`, so absence from this array is not evidence the host is down. */\n server?: string | null;\n [key: string]: unknown;\n};\n\n/**\n * Transitional shape: the legacy `{ success, message }` wrapper with the canonical error OBJECT under `error`. Emitted by partner routes that adopted the canonical `code`/`type` vocabulary without moving to the `TrillboardsError` envelope.\n */\nexport type HybridError = {\n error?: {\n /** Stable machine-readable error code. Switch on this, never on `message`. The 143 CATALOGUED codes are published as the `ErrorCode` schema and served live by `GET /v1/errors`, which is the authority. This field is NOT limited to them: services emit further codes directly, and `ApiError.notFound(resource)` mints `<resource>_not_found` from an arbitrary resource name. Always handle an unrecognised code by falling back to `type`. */\n code?: string;\n param?: string | null;\n type?: ErrorType;\n };\n message?: string;\n success?: boolean;\n};\n\n/**\n * A recorded ad impression / play for a partner screen\n * (`@trillboards/api-client` `Impression`).\n *\n */\nexport type Impression = {\n /** Kind of ad served (e.g. direct, programmatic, self_promo). */\n ad_type?: string;\n advertiser_name?: string;\n /** Effective CPM for the impression, in the partner's currency. */\n cpm?: number;\n /** Sent on the wire as `did`. The server accepts the device's Mongo id, its fingerprint, or the `partner_external_id` you registered for it. An unrecognised value is logged and the call still returns 200 — only a SIGNED impression 404s on an unknown device. */\n device_id: string;\n /** Play duration in seconds. */\n duration_seconds?: number;\n /** Earner revenue accrued for the impression. */\n earnings?: number;\n /** The per-serve id minted by the server on `data.ads[].impression_id` (`imp_<epoch-ms>_<8 hex>`) and echoed back as the `impid` parameter on the impression call — it is the dedup key, so never generate your own. Note that no endpoint currently returns this object: it is the `@trillboards/api-client` type describing what you SEND. */\n impression_id: string;\n /** The batch endpoint reads `timestamp`, NOT `played_at` — a `played_at` you send is ignored and the impression is stamped with server receive time instead. That matters for offline sync: send `timestamp` if the real play time has to survive the upload delay. */\n played_at: string;\n /** Your earner-screen id (24-character hex Mongo ObjectId), sent on the wire as `sid`. It is only a FALLBACK for resolving the screen — the server resolves from the device (`did`) first and reads `sid` solely when no device matched. */\n screen_id: string;\n};\n\n/**\n * Legacy error shape carrying a SCREAMING_SNAKE `code` alongside the message — emitted by the auth middlewares (`middleware/auth.js`, `middleware/partnerUserAuth.js`). Its `code` is NOT drawn from `ErrorCode`; it is a separate, older vocabulary.\n */\nexport type LegacyCodedError = {\n /** Legacy SCREAMING_SNAKE code, e.g. NO_TOKEN, TOKEN_EXPIRED, NO_API_KEY. */\n code?: string;\n message: string;\n success?: boolean;\n};\n\n/**\n * Legacy error shape returned by hand-authored routes that predate the canonical envelope (they call `res.status(n).json(...)` directly rather than routing through `globalErrorHandler`). Documented because it is what these endpoints really return; new endpoints must use `TrillboardsError`.\n */\nexport type LegacyError = {\n error?: string;\n message?: string;\n success?: boolean;\n};\n\nexport type Mcp_create_media_buy_Args = {\n brand?: {\n domain?: string;\n name?: string;\n [key: string]: unknown;\n };\n buy_spec?: {\n audience_profile?: Record<string, unknown>;\n budget?: {\n bid_cpm?: number;\n daily_usd?: number;\n total_usd?: number;\n };\n geo?: {\n city?: string;\n country?: string;\n lat?: number;\n lng?: number;\n radius_km?: number;\n state?: string;\n };\n schedule?: {\n days?: Array<number>;\n end_date?: string;\n hours?: Array<number>;\n start_date?: string;\n };\n screen_ids?: Array<string>;\n venue_types?: Array<string>;\n };\n buyer_ref?: string;\n context?: Record<string, unknown>;\n creative?: {\n duration_seconds?: number;\n type?: \"video\" | \"image\" | \"vast\";\n url?: string;\n };\n end_time?: string;\n idempotency_key?: string;\n name?: string;\n packages?: Array<{\n bid_price?: number;\n budget?: number | {\n amount?: number;\n currency?: string;\n [key: string]: unknown;\n };\n impressions?: number;\n pricing_option_id?: string;\n product_id?: string;\n targeting_overlay?: Record<string, unknown>;\n [key: string]: unknown;\n }>;\n start_time?: string;\n total_budget?: {\n amount?: number;\n currency?: string;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n};\n\nexport type Mcp_discover_inventory_Args = {\n audience_profile?: {\n income?: \"high\" | \"upper_mid\" | \"mid\" | \"lower_mid\" | \"low\";\n lifestyle?: string;\n min_attention?: number;\n };\n limit?: number;\n location?: {\n city?: string;\n lat?: number;\n lng?: number;\n radius_km?: number;\n state?: string;\n };\n venue_types?: Array<string>;\n};\n\nexport type Mcp_get_media_buy_delivery_Args = {\n breakdown_by?: Array<\"screen\" | \"venue\" | \"hour\" | \"audience_segment\">;\n dimensions?: Array<\"screen\" | \"venue\" | \"hour\" | \"audience_segment\" | \"creative\" | \"day\">;\n media_buy_id: string;\n [key: string]: unknown;\n};\n\nexport type Mcp_get_media_buys_Args = {\n context?: Record<string, unknown>;\n include_snapshot?: boolean;\n media_buy_ids?: Array<string>;\n pagination?: {\n cursor?: string;\n max_results?: number;\n };\n status_filter?: \"pending_creatives\" | \"pending_start\" | \"active\" | \"paused\" | \"completed\" | \"rejected\" | \"canceled\" | Array<\"pending_creatives\" | \"pending_start\" | \"active\" | \"paused\" | \"completed\" | \"rejected\" | \"canceled\">;\n [key: string]: unknown;\n};\n\nexport type Mcp_get_products_Args = {\n audience_profile?: {\n income?: string;\n lifestyle?: string;\n min_attention?: number;\n };\n brief?: string;\n buying_mode?: \"brief\" | \"wholesale\" | \"refine\";\n filters?: {\n budget_range?: {\n currency?: string;\n max?: number;\n min?: number;\n };\n channels?: Array<string>;\n countries?: Array<string>;\n delivery_type?: string;\n format_types?: Array<string>;\n venue_types?: Array<string>;\n [key: string]: unknown;\n };\n market?: string;\n pagination?: {\n cursor?: string;\n max_results?: number;\n };\n venue_types?: Array<string>;\n [key: string]: unknown;\n};\n\nexport type Mcp_list_accounts_Args = {\n account?: {\n account_id?: string;\n brand?: {\n brand_id?: string;\n domain: string;\n };\n operator?: string;\n sandbox?: boolean;\n };\n context?: Record<string, unknown>;\n pagination?: {\n cursor?: string;\n max_results?: number;\n };\n sandbox?: boolean;\n status?: \"active\" | \"pending_approval\" | \"rejected\" | \"payment_required\" | \"suspended\" | \"closed\";\n [key: string]: unknown;\n};\n\nexport type Mcp_sync_accounts_Args = {\n accounts: Array<{\n account?: {\n account_id?: string;\n brand?: {\n brand_id?: string;\n domain: string;\n };\n operator?: string;\n sandbox?: boolean;\n };\n billing?: \"operator\" | \"agent\" | \"advertiser\";\n billing_entity?: Record<string, unknown>;\n brand?: {\n brand_id?: string;\n domain: string;\n };\n notification_configs?: Array<Record<string, unknown>>;\n operator?: string;\n payment_terms?: \"net_15\" | \"net_30\" | \"net_45\" | \"net_60\" | \"net_90\" | \"prepay\";\n preferred_reporting_protocol?: string;\n sandbox?: boolean;\n }>;\n context?: Record<string, unknown>;\n delete_missing?: boolean;\n dry_run?: boolean;\n idempotency_key: string;\n push_notification_config?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\nexport type Mcp_sync_creatives_Args = {\n account?: {\n account_id?: string;\n brand?: {\n brand_id?: string;\n domain: string;\n };\n operator?: string;\n sandbox?: boolean;\n };\n assignments?: Array<{\n creative_id: string;\n media_buy_id?: string;\n package_id?: string;\n [key: string]: unknown;\n }>;\n context?: Record<string, unknown>;\n creative_ids?: Array<string>;\n creatives: Array<{\n assets?: Record<string, unknown>;\n creative_id?: string;\n duration_seconds?: number;\n file_size_mb?: number;\n format_id?: {\n agent_url: string;\n duration_ms?: number;\n height?: number;\n id: string;\n width?: number;\n } | string;\n format_kind?: string;\n height?: number;\n name?: string;\n type?: \"video\" | \"image\" | \"vast\";\n url?: string;\n width?: number;\n [key: string]: unknown;\n }>;\n delete_missing?: boolean;\n dry_run?: boolean;\n ext?: Record<string, unknown>;\n idempotency_key?: string;\n media_buy_id?: string;\n push_notification_config?: Record<string, unknown>;\n validation_mode?: string;\n [key: string]: unknown;\n};\n\nexport type Mcp_update_media_buy_Args = {\n account?: Record<string, unknown>;\n canceled?: boolean;\n cancellation_reason?: string;\n context?: Record<string, unknown>;\n end_time?: string;\n ext?: Record<string, unknown>;\n idempotency_key?: string;\n invoice_recipient?: Record<string, unknown>;\n media_buy_id: string;\n new_packages?: Array<Record<string, unknown>>;\n packages?: Array<{\n creative_assignments?: Array<{\n creative_id: string;\n [key: string]: unknown;\n }>;\n package_id: string;\n targeting_overlay?: Record<string, unknown> | null;\n [key: string]: unknown;\n }>;\n paused?: boolean;\n push_notification_config?: Record<string, unknown>;\n reporting_webhook?: Record<string, unknown>;\n revision?: number;\n start_time?: string | string;\n updates?: {\n budget?: {\n bid_cpm?: number;\n daily_usd?: number;\n total_usd?: number;\n };\n end_date?: string;\n name?: string;\n screen_ids?: Array<string>;\n start_date?: string;\n status?: \"active\" | \"paused\" | \"cancelled\";\n targeting?: Record<string, unknown>;\n };\n [key: string]: unknown;\n};\n\nexport type MdnsNetworkDevice = {\n /** The responder's IP address as a string — the first IPv4 address if it has one, otherwise any address. A record that resolves to no address is dropped rather than emitted with a null host, so in practice this is always populated. It is also what seeds the HTTP-probe target list for the same discovery cycle. */\n host?: string | null;\n /** The service instance name verbatim from the resolve event. A PII filter runs first and DROPS THE WHOLE ROW when the name looks like a personal name (an apostrophe-s possessive, an email, a MAC), so devices named after people disappear from the list entirely rather than appearing anonymised. */\n instanceName: string;\n /** Model string lifted from the TXT record using a per-service-type key table — `md` for Chromecast and HAP, `model`/`am` for AirPlay, `ty`/`product` for IPP, `dt` for Matter (a device-TYPE code used as a model surrogate, not a model name), with a broader default list for other types. `null` when no key in that list is present or all their values are blank. */\n mdnsModel?: string | null;\n /** Version string from the TXT record. Frequently this is `txtvers` — the TXT RECORD FORMAT version, not the device firmware version — because that key is in the fallback list for most service types. `null` when no candidate key is present. */\n mdnsSoftwareVersion?: string | null;\n /** Vendor string from the TXT record, by the same per-type key table. Two traps: Chromecast has an EMPTY vendor key list, so it is always `null` there; and for HAP this carries the HomeKit `ci` CATEGORY id (a numeric device class) while for Matter it carries the raw `VP` `vendorid+productid` pair — neither is a vendor name. */\n mdnsVendor?: string | null;\n /** TCP/UDP port advertised in the SRV record. A port of 0 is coerced to `null` rather than forwarded, so `null` means \"not advertised\", never \"port zero\". */\n port?: number | null;\n /** The mDNS service type the record resolved under, e.g. `_googlecast._tcp.`. Discovery is not open-ended — the scanner browses a FIXED list of 19 types (Roku, AirPlay, RAOP, Chromecast, Amazon WPlay, Android TV Remote, Spotify Connect, device-info, companion-link, IPP/IPPS/printer/PDL, HAP, Matter, Alexa, HTTP and the Trillboards CSI type), so a device advertising anything else is invisible here. */\n serviceType: string;\n [key: string]: unknown;\n};\n\n/**\n * Versioned public disclosure of how audience measurement is produced.\n * Describes WHAT is measured and HOW IT IS QUALIFIED — deliberately never\n * names infrastructure or source locations.\n *\n */\nexport type MeasurementMethodology = {\n /** Prose description of the happy-path pipeline: edge capture, buffered ingest, `signal_observations`, a 30-minute device-resolution pass into `resolved_device_observations`, then a 30-minute transition pass. Names the stores only — never infrastructure or source locations. */\n canonical_path?: string;\n /** What each screen observes and sends. */\n capture_stack?: Record<string, unknown>;\n /** Cross-day identity, including measured coverage (1.6% of identifiable BLE rows). An earlier revision said ~31%; that graded the presence of stable_payload_hash and ignored the sanitizer the identity expression applies, and it is withdrawn. */\n cross_day_matching?: Record<string, unknown>;\n /** The in-range contour: BLE RSSI at or above -75 dBm, empirically about 5 m line-of-sight for Class-2 radios at typical mounting. Calibration is static per screen-class — there is no per-screen ranging today — and devices inside the contour classified as stationary are excluded from the audience funnel by default. */\n display_exposure_zone?: Record<string, unknown>;\n /** What promotes a raw in-range observation to a qualifying event, one entry per qualification (`ble_engagement`, `face_attention`, `returning_visitor`), each carrying its definition, its rationale, and the ClickHouse field a reviewer re-derives it from. */\n dwell_qualification_thresholds?: Record<string, unknown>;\n /** Static disclosure of how observation confidence decays: `algorithm` (Bayesian posterior with temporal decay), `decay_window_days` (90) and `max_confidence` (0.95). These are published constants, not per-request measurements. */\n edge_confidence_model?: Record<string, unknown>;\n /** Evidence grades, which are decisionable, and person-grain resolution. */\n evidence_grade_ladder: Record<string, unknown>;\n /** Prose description of degraded ingest: observations buffer on a durable queue and replay on recovery, and after repeated failures land in a dead-letter store rather than being dropped. */\n fallback_path?: string;\n /** How identifiers are keyed, and what erasure means. */\n hashing?: Record<string, unknown>;\n /** The counting UNIT. Reach and unique-device counts are counts of distinct canonical BLE keys (identifiers); one device contributes several identifiers per window and the number grows with the window. Carries the measured identifiers-per-device per fleet type and the controlled comparison it was derived from. */\n identifier_inflation?: Record<string, unknown>;\n /** Alignment against MRC OOH Phase 2. `mrc_status_accredited` is the machine-readable answer and is **false until an MRC grant reference exists** — read that boolean rather than parsing the English `status` line. Also carries the monotonic qualification chain (`in_range` through `cross_screen`) and the interval method (Wilson score for proportions, 200-sample bootstrap for medians). */\n mrc_ooh_phase2_alignment: Record<string, unknown>;\n /** Version markers for the edge parsers that produced the disclosed fields — `ble_advertisement_parser`, `mdns_txt_extractor`, and the integer `heartbeat_payload_schema`. Hand-maintained constants, bumped when a parser changes. */\n parser_versions?: Record<string, unknown>;\n /** Per-store retention in days in active systems; `basis` states how each is counted and what the encrypted archive keeps. */\n retention: Record<string, unknown>;\n /** Tables a reviewer re-derives every published count from. */\n source_tables: Array<string>;\n /** Bumped on any disclosure change; drives the ETag. */\n version: string;\n};\n\n/**\n * Revenue per screen per hour. Aggregates gross revenue from completed impressions. Refreshed from CH via clickhouseRollupSource. PG only.\n */\nexport type OpenrtbRevenueScreenHourly = {\n actual_gross_revenue: number;\n estimated_gross_revenue: number;\n estimated_impressions: number;\n gross_revenue: number;\n hour_bucket: string;\n impressions: number;\n priced_impressions: number;\n screen_mongo_id: string;\n updated_at: string;\n};\n\nexport type Pagination = {\n /** Whether more items exist */\n has_more?: boolean;\n /** Items per page */\n limit?: number;\n /** Current offset */\n offset?: number;\n /** Total number of items */\n total?: number;\n};\n\nexport type Partner = {\n /** Defaults to the literal `lite_sdk` when unset, so it is never null, and is `lite_sdk` for effectively the whole fleet. It is load-bearing at serve time: `lite_sdk`/`full_sdk` mark the partner a video-only SDK consumer, so an image creative is swapped for the ad's partner video URL unless `sdk_config.allowed_ad_types` includes `image`. */\n integration_tier?: string;\n /** Registered company name. The same string is the buyer-facing media-owner label for this partner's inventory, so it is what advertisers see in the media-owner picker. */\n name?: string;\n /** The 24-character hex Mongo-era identifier (`partners.mongo_partner_id`), not the table's integer `partner_id`. This is the value the API key's owner id and every `partner_mongo_id` foreign key carry. */\n partner_id?: string;\n /** Legacy classification retained for existing integrations. New integrations should not branch on this field. */\n partner_type?: string;\n /** The partner's share as a percentage 0-100 (the stored rate is 0-1 and is multiplied by 100 here). Resolved from the active, currently-effective `publisher_payout_rates` rule; with no such rule it falls through to the system default and reports `60` — which is also why the `POST /register` response echoes `60` even when the caller asked for 70, since the partner row is read back before the rate row is written. */\n revenue_share_percent?: number;\n sdk_config?: SDKConfig;\n /** Lower-cased at registration and unique across all partners (a collision is rejected `409`). It appears in device embed URLs and portal links, so it is effectively permanent once devices are provisioned. */\n slug?: string;\n stats?: PartnerStats;\n /** In practice **always `active` here**: the API-key resolver refuses any partner whose status is not `active`, so a `pending`/`suspended`/`terminated` partner gets `401 invalid_api_key` and never reaches this response. Registration also always creates `active`. */\n status?: \"pending\" | \"active\" | \"suspended\" | \"terminated\";\n /** Defaults to the literal `pending` when unset. `POST /earnings/payout` refuses any value other than `active`. */\n stripe_account_status?: string;\n};\n\nexport type PartnerAdDeliveryProfile = {\n /** Android SDK API level as reported by the device; `null` when not reported, and then no Android rule fires at all. Below 23 forces `mode: vast_fallback` and turns `auto_recovery_enabled` off. 23-25 with desugaring reported off only appends a warning to `notes` and leaves the mode alone. */\n android_api_level?: number | null;\n /** Whether the SERVER may auto-enqueue recovery device commands for this device — the `refresh_ads` and `restart` it issues when a screen heartbeats online without recent VAST requests, or with stalled playback. It is not a client-side setting, and it goes false alongside every `vast_fallback` verdict so we do not restart a device whose player we already know is the wrong one. Even when true, a recovery command is queued only for a device that acts on commands, meaning one that acknowledged a command (`POST /device/{deviceId}/command/{commandId}/ack`) in the last 7 days, or that was handed none in that time. A device that let a delivered command expire unacknowledged, and acknowledged none, is sent no recovery command until that command is 7 days old. */\n auto_recovery_enabled?: boolean;\n /** Echoed back from the device's own heartbeat telemetry, or `unknown` when the device never reported one. It selects which compatibility rules run: `android_native` enables the Android API-level gate, `html5_webview` the Chromium-version checks; `disabled` and `unknown` skip both, so no mode change can result from those. */\n ima_integration?: \"android_native\" | \"html5_webview\" | \"disabled\" | \"unknown\";\n /** The server's per-heartbeat verdict on how this device should play ads: `ima_sdk` is the normal IMA path, `vast_fallback` means bypass IMA entirely and play the VAST in your own fallback player. It is recomputed from the telemetry on every heartbeat, so treat it as a live instruction rather than one-time provisioning. */\n mode?: \"ima_sdk\" | \"vast_fallback\";\n /** Human-readable diagnostics accompanying the decision above; an empty array on a healthy device. They are free prose for logs and support tickets, not a stable enum — nothing in the contract depends on matching their text, and their presence alone does not imply degraded delivery. */\n notes?: Array<string>;\n /** Machine token for why `mode` was chosen: `default` (nothing disqualified the device), `sdk_reported_ima_unsupported` (your own telemetry reported `imaSupported: false`), or `android_api_below_23_for_current_ima_android_sdk`. `default` is always what accompanies `ima_sdk`. */\n reason?: string;\n /** Major Chromium version of the device's WebView, `null` when not reported. Advisory only — it never changes `mode`: below 87 appends a content-restoration note and below 80 an additional autoplay/media note. */\n webview_chromium_major?: number | null;\n};\n\n/**\n * Non-fatal advisories about the creative that was just accepted. The ad IS created and will serve — these describe problems only the uploader can fix. Empty when there is nothing to report. The most important one is an over-length video: a spot longer than the 30 second ad slot is cut off when the next ad starts, so it never reaches its completion event and its final seconds are never seen.\n */\nexport type PartnerCreativeWarnings = Array<string>;\n\nexport type PartnerDefaultStream = {\n /** The stream's 24-hex id, minted server-side with `newObjectId()` at create and stored as `default_streams.mongo_default_stream_id`. It is the value every other default-stream route takes as `{streamId}`; the shape is Mongo's but PostgreSQL is the store of record. */\n _id?: string;\n /** Accepted with no required-field check, same as `google_slides_url`. The derived `canva_design_id` is returned alongside it although this schema does not declare it. */\n canva_url?: string | null;\n /** Mapped from `default_streams.created_at`. It is the list endpoint's secondary sort key, so two streams sharing a `playback_order` play oldest-first. */\n createdAt?: string;\n /** Falls back to `name`, then to `Background content`; the upload and image routes use `Uploaded video content` / `Image carousel content` instead. */\n description?: string;\n /** Seconds one pass of this stream occupies. It is computed as `images.length x image_duration` when you send images without it; for every other type it is only ever what you sent — nothing probes a YouTube or uploaded video to fill it in on this path. */\n duration_seconds?: number | null;\n /** The URL the player actually loads into the iframe. It defaults to `generic_iframe_url` when you do not send it, so on an iframe stream the two are usually equal. */\n embed_url?: string | null;\n /** The iframe source for the `generic_iframe` type. It also seeds `embed_url` when that is omitted, so sending only this one populates both fields. */\n generic_iframe_url?: string | null;\n /** Unlike `youtube`/`twitch`/`uploaded`/`images`, the `google_slides` type has NO required-field check — a create naming that type with no URL is accepted, stores null, and the screen then renders an empty frame. */\n google_slides_url?: string | null;\n /** Only five keys survive: `auto_advance`, `slide_delay_ms`, `refresh_interval_seconds`, `sandbox_flags`, `allow_flags` — anything else is silently discarded and a non-object is discarded whole. On update it is MERGED over the stored config rather than replacing it, so a key cannot be cleared by omitting it. */\n iframe_config?: Record<string, unknown> | null;\n /** Seconds each image is held before advancing. The multipart image route defaults it to 10 when omitted and uses it to compute `duration_seconds`; the JSON create leaves it unset unless you send it. */\n image_duration?: number | null;\n /** Ordered image URLs, each held for `image_duration` seconds. Required and non-empty when `stream_type` is `images` (400 otherwise), null for every other type; the create accepts a real array, a JSON-encoded array string, or a comma-separated string. */\n images?: Array<string>;\n /** Soft-deactivation flag. The list filters `is_active = true`, so it is always `true` on anything this API returns; the partner DELETE removes the row outright rather than clearing this flag, which is instead used by the screen-wide deactivate paths. */\n is_active?: boolean;\n /** At most one stream per screen carries this — promoting or reordering clears it on every sibling first. A create sets it on the new stream, `reorder` gives it to `order[0]`, and deleting the current stream hands it to whatever the ordering resolves next. */\n is_currently_playing?: boolean;\n /** Falls back to `description`, then to the literal `Default Stream`, so it is never empty. The multipart upload route instead falls back to the uploaded file's original name, and the multi-image route to `Image Carousel (N images)`. */\n name?: string;\n /** Present ONLY on the 201 from the three create routes, which echo the in-memory document back. `default_streams` has no partner column, so list, update and read responses omit this key entirely — tenancy is enforced at the screen, not on the stream row. */\n partner_id?: string;\n /** 1-based position in the screen's rotation; the list sorts on it and breaks ties with `createdAt`. A newly created stream is promoted to 1 and every sibling shifts down, and `POST .../reorder` rewrites it to each id's index in the `order` array you send. */\n playback_order?: number;\n /** The owning screen, mapped from `default_streams.screen_mongo_id` — the same 24-hex id you passed as `{screenId}` in the path. Note the camelCase spelling inside an otherwise snake_case object. */\n screenId?: string;\n /** Accepted with no required-field check. The derived `social_wall_provider` and `social_wall_widget_id` are returned alongside it although this schema does not declare them. */\n social_wall_url?: string;\n /** Which renderer the screen uses. When omitted at create it is DERIVED from whichever content field you sent, in this fixed order: `images`, `youtube_url`, `twitch_url`, `google_slides_url`, `canva_url`, `social_wall_url`, `generic_iframe_url`/`embed_url`, `video_url` (`uploaded`), `local_file_path` (`local`) — and the create is rejected 400 if none resolves. Only `youtube`, `uploaded`, `images` and `twitch` are ever content-analyzed; the four embed types deliberately keep `gemini_analyzed_at` NULL. */\n stream_type?: string;\n /** Required when `stream_type` is `twitch` (400 otherwise). The response also carries the undeclared `twitch_embed_url`, `twitch_content_type` and `twitch_id` companions that the PG reshape returns. */\n twitch_url?: string | null;\n /** Mapped from `default_streams.updated_at`. Playback bookkeeping writes it — marking a stream current, clearing a stale current flag, or stamping `last_played_at` — so it moves without anyone editing the stream content. */\n updatedAt?: string;\n /** Required when `stream_type` is `uploaded`. The `/default-streams/upload` route sets it for you to the stored object URL of the file you posted (`req.file.location`), so you never send it on that path. */\n video_url?: string | null;\n /** Required when `stream_type` is `youtube` — the create is rejected 400 (`youtube_url is required for youtube streams`) without it. Null on every other stream type. */\n youtube_url?: string | null;\n};\n\nexport type PartnerDefaultStreamCreate = {\n /** Accepted with no required-field check, same as `google_slides_url` — a `canva` create with this omitted succeeds and renders nothing. */\n canva_url?: string;\n /** Optional. Falls back to `name` and then to `Background content`. */\n description?: string;\n /** Optional explicit duration for iframe-based streams */\n duration_seconds?: number;\n /** The URL actually loaded into the iframe. When omitted it is filled from `generic_iframe_url`, so send it only when the embeddable URL differs from the one you want recorded. */\n embed_url?: string;\n /** The iframe source. Sending it also derives `stream_type: generic_iframe` and seeds `embed_url`, so this one field is enough to create an iframe stream. */\n generic_iframe_url?: string;\n /** The `google_slides` type has NO required-field check, so a create naming that type without this URL is accepted and stores null. Send a published-to-web slides URL. */\n google_slides_url?: string;\n /** Only these five keys are kept — every other key you send is silently dropped, a non-object is dropped whole, and `slide_delay_ms`/`refresh_interval_seconds` must be finite numbers or they are dropped individually. On a later update this object is merged over the stored one rather than replacing it. */\n iframe_config?: {\n allow_flags?: string;\n auto_advance?: boolean;\n refresh_interval_seconds?: number;\n sandbox_flags?: string;\n slide_delay_ms?: number;\n };\n /** Seconds per image */\n image_duration?: number;\n /** Required and non-empty for an `images` stream. Accepted as a real array, a JSON-encoded array string, or a comma-separated string; combined with `image_duration` it also derives `duration_seconds` when you do not send one. */\n images?: Array<string>;\n /** Optional. Falls back to `description` and then to the literal `Default Stream`, so the created stream always has one. */\n name?: string;\n /** Accepted with no required-field check — a `social_wall` create with this omitted succeeds and renders nothing. */\n social_wall_url?: string;\n /** Optional — omit it and the server DERIVES it from whichever content field you sent, checking in this order: `images`, `youtube_url`, `twitch_url`, `google_slides_url`, `canva_url`, `social_wall_url`, `generic_iframe_url`/`embed_url`, `video_url` (`uploaded`), `local_file_path` (`local`). If nothing resolves the request is rejected 400 `No supported stream content provided`. */\n stream_type?: \"local\" | \"youtube\" | \"uploaded\" | \"images\" | \"twitch\" | \"google_slides\" | \"canva\" | \"social_wall\" | \"generic_iframe\";\n /** Required when the resolved `stream_type` is `twitch`; the create is rejected 400 without it. */\n twitch_url?: string;\n /** Required when the resolved `stream_type` is `uploaded`. Use `POST .../default-streams/upload` with a multipart file instead if you want us to host the video — that route fills this in for you. */\n video_url?: string;\n /** Required when the resolved `stream_type` is `youtube`; the create is rejected 400 without it. Sending it alone is enough — the type is derived. */\n youtube_url?: string;\n};\n\nexport type PartnerDefaultStreamUpdate = PartnerDefaultStreamCreate;\n\n/**\n * Batch device-registration body (`@trillboards/api-client`\n * `PartnerDeviceBatchRegistrationInput`) — up to 100 devices with optional\n * fleet-wide `defaults`. `defaults` applies to every device in the batch\n * except the per-device identifier fields (`device_id` / `device_name`).\n *\n */\nexport type PartnerDeviceBatchRegistrationInput = {\n /** Fleet-wide defaults applied to each device except its identifier fields. */\n defaults?: PartnerDeviceRegistrationInput;\n /** 1 to 100 entries; more than 100 rejects the whole request with `Maximum 100 devices per batch request`. The batch is NOT transactional — entries are registered sequentially and independently, so the call can return 2xx with some entries reported under `errors[]` while the rest committed. An entry whose `device_id` already exists is an update, counted under `summary.updated` rather than `summary.created`. */\n devices: Array<PartnerDeviceRegistrationInput>;\n};\n\n/**\n * Heartbeat payload from agent-core / agent-core-lite Android partner\n * SDKs. Only includes fields verified populated in production\n * `signal_observations` (last 30 days). Stub fields wired in Kotlin but\n * never observed in CH are intentionally excluded; re-add only after\n * telemetry confirms >1% population over 7 days.\n *\n */\nexport type PartnerDeviceHeartbeatBody = {\n /** Legacy flat advertising identifier, superseded by the `advertising_identity` envelope. It is only honoured when no canonical envelope is present AND it passes a plausibility check; a value that fails the check is dropped without an error, and it never reaches the device record through the generic device snapshot — identity has one dedicated persistence rail. */\n advertisingId?: string | null;\n /** Type of the legacy flat `advertisingId`. It is retained only when `advertisingId` itself survives the plausibility check — otherwise it is nulled with it — and the canonical `advertising_identity.id_type` enum is wider (`gaid`, `aaid`, `afai`, `rida`, `tifa`, `idfa`) than the two values allowed here. */\n advertisingIdType?: \"gaid\" | \"idfa\" | null | null;\n advertising_identity?: AdvertisingIdentity;\n /** Number of distinct BLE devices seen in the scan window, BEFORE the 50-entry cap on `nearbyBleDevices` — so it can legitimately exceed the array length (the on-device scanner tracks up to 100 groups). Omit it and the server substitutes the array length. Both this and the snake_case `ble_device_count` are accepted. */\n bleDeviceCount?: number;\n capabilities?: DeviceCapabilityPayload;\n /** mDNS/Bonjour service instances found on the LAN, capped at 50. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`discovered_network_devices`). */\n discoveredNetworkDevices?: Array<MdnsNetworkDevice> | null;\n /** Up to 16 audience identities (hashed email / UID2) keyed by `(id_type, source)`. Rejection is all-or-nothing — one bad element or a 17th entry drops the whole array — and the row-level upsert additionally refuses any envelope whose `observed_at` is older than the stored one, so a replayed batch is a silent no-op. */\n extended_identities?: Array<ExtendedIdentity>;\n /** The agent's stable device fingerprint. On this endpoint it is inert: the path parameter `deviceId` is the sole binding boundary, and the partner heartbeat controller never reads `fingerprint` from the body — it uses the fingerprint of the device the path already resolved. */\n fingerprint?: string;\n /** Hash of the LAN default-gateway IP — the venue-network join key, and the seed the HTTP probe adds to its mDNS-derived target list. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`gateway_ip_hash`). */\n gatewayIpHash?: string | null;\n /** Results of HTTP HEAD probes against at most 10 hosts — the mDNS hosts found in the same cycle plus the Wi-Fi gateway, port 80 only. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`http_probes`). */\n httpProbes?: Array<HttpProbeResult> | null;\n /** Legacy flat LAT flag. `true` alone is enough to clear a previously stored identifier — the server synthesises a `limited` identity envelope from it even with no `advertisingId` present — so do not send `true` speculatively. */\n limitAdTracking?: boolean | null;\n /** Alternate namespace for the same telemetry keys as `telemetry`, merged UNDER it (`{...metadata, ...telemetry}`), so `telemetry` wins on a collision. This is where the Android agents actually put their telemetry — `uptimeSeconds`, `agentType`, `agentVersion`, screen geometry, `sensingMode` — and where the per-heartbeat CPU/memory/thermal gauges are merged in. */\n metadata?: Record<string, unknown>;\n /** Capped at 50 devices (RSSI-sorted, strongest first). */\n nearbyBleDevices?: Array<BleScanResultData> | null;\n /** Wi-Fi scan results, capped at 50 with the joined AP pinned first. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`nearby_wifi_networks`, whose inner keys are `raw_bssid`/`signal_dbm`/`frequency_mhz` — note `signal_dbm`, not a snake_case form of `signalStrengthDbm`). */\n nearbyWifiNetworks?: Array<WifiScanResult> | null;\n /** Count of mDNS instances discovered, before the 50-entry cap (the Rust scanner itself keeps at most 30). Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`network_device_count`). */\n networkDeviceCount?: number;\n /** Heartbeat schema version (1=baseline, 2=nativeSensorSnapshot) */\n schemaVersion?: number;\n /** The screen the agent believes it is bound to. Also inert on this endpoint — the server resolves the screen from the device record, never from this value; correct a wrong binding through registration or the socket heartbeat, which returns an authoritative screen id. */\n screenId?: string | null;\n /** Per-source skip-reason counter map. Shape `{source: {reason: count}}`. */\n skipReasonCounts?: Record<string, Record<string, number>>;\n /** UPnP/SSDP responders found by one `M-SEARCH` with `ST: ssdp:all`, deduped by LOCATION and capped at 50. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`ssdp_devices`). */\n ssdpDevices?: Array<SsdpDeviceInfo> | null;\n /** Agent lifecycle word — the Android agents hardcode `\"active\"` on every heartbeat, so it carries no information. The partner heartbeat controller does not read it; online/standby state is derived from `telemetry.powerState`, not from this field. */\n status?: string;\n /** Hash of the joined access point's BSSID. This field is accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`wifi_bssid_hash`). */\n wifiBssidHash?: string | null;\n wifiEnvironment?: WifiEnvironmentSnapshot;\n /** Count of distinct access points seen, before the 50-entry cap on `nearbyWifiNetworks`. Accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`wifi_network_count`). */\n wifiNetworkCount?: number;\n /** Hash of the joined network's SSID. Same as `wifiBssidHash`: accepted by the schema but NOT forwarded by the partner heartbeat handler — that controller contains no reference to wifi/mdns/ssdp/http-probe fields at all and passes only the BLE block to the ingest pipeline. The shipped Android agent sends these to the FIRST-PARTY heartbeat instead, in snake_case (`wifi_ssid_hash`). */\n wifiSsidHash?: string | null;\n [key: string]: unknown;\n};\n\n/**\n * A play reported by the partner SDK's reportPlay. The PartnerPlay body without screen_id (the screen is the device's binding), plus the Trillboards impression id.\n */\nexport type PartnerDevicePlay = {\n /** Optional ad/line identifier; defaults to the sha-keyed creative. */\n ad_id?: string;\n /** When the play completed (device clock), within the last 7 days and at most 5 minutes ahead of the server clock. */\n completed_at: string;\n /** The classification reference from POST /creatives — `sha256:<hex>` or the bare 64-char content hash. */\n creative_ref: string;\n /** Play duration in seconds (default 30). */\n duration_seconds?: number;\n /** The Trillboards impression id, when the host played an ad Trillboards served. The Trillboards record of that play is then kept and this report is not counted twice. */\n impression_id?: string;\n /** Your unique id for this play (idempotency key, shared with POST /v1/partner/plays). */\n play_id: string;\n /** Optional request identifier. */\n request_id?: string;\n};\n\n/**\n * Request body accepted by the single-device registration endpoints\n * (`@trillboards/api-client` `PartnerDeviceRegistrationInput`). Additional\n * vendor-specific fields are accepted and forwarded to enrichment.\n *\n */\nexport type PartnerDeviceRegistrationInput = {\n /** Stable partner-owned device identifier. */\n device_id?: string;\n /** Legacy SDK alias for the device identifier, retained for backwards compatibility. */\n device_name?: string;\n /** Mutable partner category stored independently of advertising classification. Defaults to other on a new device; omission on an existing device preserves its value. Unsupported categories return HTTP 400. */\n device_type?: \"tv\" | \"tablet\" | \"display\" | \"kiosk\" | \"other\" | \"mobile\" | \"connected_tv\" | \"digital_signage\" | \"vending_machine\";\n /** Display specification (pixel resolution and/or physical size). */\n display?: Record<string, unknown>;\n display_settings?: PartnerRegistrationDisplaySettings;\n /** Location hints (lat/lng, address, venue type). */\n location?: Record<string, unknown>;\n /** Friendly name for the device. */\n name?: string;\n /** Placement hints for the screen within the venue. */\n placement?: Record<string, unknown>;\n /** Venue taxonomy hints. */\n venue?: Record<string, unknown>;\n [key: string]: unknown;\n};\n\nexport type PartnerHeartbeatCommand = {\n /** Command normalized for partner SDK compatibility. */\n command?: string;\n /** Opaque command identifier — echo it verbatim to `POST /device/{deviceId}/command/{commandId}/ack`. The ack is ownership-checked against the device that received it, so acknowledging another device's command is refused with 403. */\n id?: string;\n /** Command arguments, shape determined by `command` (for example a brightness level or a target URL). Always an object — commands that take no arguments carry `{}`, never null. */\n payload?: Record<string, unknown>;\n /** When the command was enqueued, not when it was dispatched to you — the gap is up to one heartbeat interval, and a large gap means the device was silent, not that the server was slow. */\n queued_at?: string;\n};\n\nexport type PartnerHeartbeatData = {\n ad_delivery_profile?: PartnerAdDeliveryProfile;\n /** Pending remote commands, at most 5 per heartbeat, drained from the PG `device_commands` store. Each is marked `sent` as it is handed to you, so it appears in exactly ONE heartbeat response and is not re-delivered — acknowledge every entry via the ack endpoint, and dedupe on `id` because delivery is at-most-once, not exactly-once. */\n commands?: Array<PartnerHeartbeatCommand>;\n /** Server-clock timestamp stamped when this heartbeat was processed — the same instant used for the liveness write, not a device-supplied time and not the time of a previous heartbeat. */\n last_seen_at?: string;\n /** Liveness verdict for the screen. In practice it is `online`, or `standby` when the heartbeat reported `power_state` of `off`/`standby` — `standby` is NOT in the declared enum, and `offline` can never be returned here because receiving this response is itself proof the device reached us. It falls back to `online` when the device is not yet linked to a screen. */\n status?: \"online\" | \"offline\";\n};\n\nexport type PartnerHeartbeatMeasurementMetadata = {\n /** Whole seconds since `receivedAt`, computed when the response was built and floored at 0 — the same number as the sibling `stalenessSeconds`. It measures the age of the HEARTBEAT, not of the cache entry; use `cachedAt` for cache age. */\n ageSeconds?: number | null;\n /** When the Redis device twin that served this response was written. It is non-null ONLY when `source` is `redis_twin`, and even then only if the twin carried a `cachedAt`/`observedAt` stamp; the `pg_fallback` and `unknown` paths always report `null`. */\n cachedAt?: string | null;\n /** ISO-8601 server-clock time the underlying heartbeat was received — byte-for-byte the same value as the sibling `lastHeartbeatAt`, not an independent measurement. `null` when no heartbeat has ever been recorded. */\n receivedAt?: string | null;\n /** Which store answered: `redis_twin` (the hot pre-computed device twin — the normal path), `pg_fallback` (the twin was cold or missing, so the persisted last-heartbeat record was used), or `unknown` (no heartbeat has ever been recorded). This is the field that determines which keys `telemetry` can contain, and it is the only signal that the twin was a miss. */\n source?: \"redis_twin\" | \"pg_fallback\" | \"unknown\";\n [key: string]: unknown;\n};\n\nexport type PartnerHeartbeatQueryData = {\n adDeliveryProfile?: PartnerAdDeliveryProfile;\n /** The device's canonical identifier, which need NOT equal the value you put in the path: the server echoes the device fingerprint, falling back to your registered `external_id` and then to the internal id. Match responses on your own request, not on this field. */\n deviceId: string;\n /** Server-clock time the most recent heartbeat was RECEIVED — never a device-supplied timestamp. `null` means this device has never sent a heartbeat that the server retained; note the value can come from the Redis twin, which expires, so a cold cache falls back to the persisted record rather than reporting null. */\n lastHeartbeatAt?: string | null;\n measurementMetadata?: PartnerHeartbeatMeasurementMetadata;\n /** Whole seconds between `lastHeartbeatAt` and the moment this response was built, floored at 0 and rounded — it is computed at read time, so two calls a minute apart return different values for the same heartbeat. `null` when `lastHeartbeatAt` is null. It duplicates `measurementMetadata.ageSeconds` exactly. */\n stalenessSeconds?: number | null;\n telemetry?: PartnerSafeHeartbeatTelemetry;\n [key: string]: unknown;\n};\n\nexport type PartnerHeartbeatQueryResponse = {\n data?: PartnerHeartbeatQueryData;\n /** Always `true` on this 200 response. The 400 (missing `deviceId`), 404 (unknown device), 403 (device belongs to another partner) and 500 paths return `success: false` with a `message` and no `data`. */\n success?: boolean;\n};\n\n/**\n * Optional heartbeat payload. Nested telemetry objects are preferred; legacy flat keys\n * are still accepted for backward compatibility. The registered\n * `deviceId` is the binding boundary for both liveness and identity; no\n * separate partner API key is required.\n *\n */\nexport type PartnerHeartbeatRequest = {\n /** Highest-precedence source for `last_vast_request_at`, `last_vast_response_at` and `proof_of_play_enabled`. All three are dead on both sides — no agent or SDK in the repo emits this block, and nothing reads the values once stored. */\n ad?: {\n last_vast_request_at?: string;\n last_vast_response_at?: string;\n proof_of_play_enabled?: boolean;\n };\n advertising_identity?: AdvertisingIdentity;\n /** Flat alias of `telemetry.agentStatus`. No producer uses this spelling; live values arrive camelCase and come from two disjoint vocabularies — `sensing`/`camera_failed`/`starting`/`idle` from the Android sensing SDK, `foreground`/`background` from the browser SDK. */\n agent_status?: string;\n /** Second-precedence source (after `telemetry.agentVersion`) for the Trillboards agent/SDK version — this is the key the Android sensing SDK actually sends on the partner heartbeat. It is kept ONLY if the heartbeat self-identifies as a Trillboards SDK; otherwise the server force-nulls it so a BYO player's app version is never recorded as ours. */\n agent_version?: string;\n /** Android SDK/API level; below 23 forces `vast_fallback` for a native-Android IMA host. The shipped browser SDK derives it from the user-agent token `Android <n>`, which is the platform RELEASE number (Android 9 gives 9), not the API level (28) — so a value under ~23 is more likely a release number than a genuinely old device. */\n android_api_level?: number;\n /** Flat alias of `telemetry.brightness`, on the platform's own 0-100 scale. Only the Tizen and webOS agents emit a brightness at all, and they put it in `metadata`, not here. */\n brightness?: number;\n /** Java 8+ library desugaring in a native-Android IMA host. Nothing in the repo emits it in any casing, so it is absent fleet-wide; it is read only when `ima_integration` is `android_native` and `android_api_level` is 25 or below, where `false` adds a crash-risk note to `ad_delivery_profile.notes`. */\n desugaring_enabled?: boolean;\n /** Hardware self-description. `os`/`osv`/`make`/`model` here rank below `telemetry`/`metadata` and above the flat body keys, while `android_api_level` and `webview_chromium_major` are read from here in snake_case. It also carries the undocumented `native_bridge` object — the Android WebView bridge's ground-truth make/model/OS/advertising identity — and the DOOH taxonomy keys `dth`/`msid`/`venuetype`/`rdid`/`idtype`/`is_lat`. */\n device?: {\n android_api_level?: number;\n make?: string;\n model?: string;\n os?: string;\n osv?: string;\n webview_chromium_major?: number;\n };\n /** Up to 16 DSP-matchable audience identities (hashed email / UID2). Normalisation is ALL-OR-NOTHING across the array: one malformed element, a duplicate `(id_type, source)` pair beyond the first, or more than 16 entries causes the ENTIRE array to be dropped and the heartbeat still answers 200. Send `[]`-worth of care — a partially valid batch stores nothing. */\n extended_identities?: Array<ExtendedIdentity>;\n /** Flat alias, outranked by `ima.integration`. `html5_webview` marks the heartbeat as coming from a Trillboards SDK (which is what permits `agent_version` to be stored); `android_native` with an API level below 23 forces `vast_fallback`. Unrecognised or absent values become `unknown`, never null. */\n ima_integration?: \"android_native\" | \"html5_webview\" | \"disabled\" | \"unknown\";\n /** Flat alias with no producer — the browser SDK sends `ima.sdk_version` (from `window.google.ima.VERSION`), which outranks this key and is `null` when IMA failed to load. */\n ima_sdk_version?: string;\n /** Flat alias with no producer — the browser SDK sends `ima.support_reason`, the literal `ok` on success or its IMA load-failure reason. Stored only; the ad-delivery decision never reads it. */\n ima_support_reason?: string;\n /** Flat alias, outranked by `ima.supported` and by `telemetry`/`metadata`. An explicit `false` pins `ad_delivery_profile.mode` to `vast_fallback` and turns `auto_recovery_enabled` off, so the server stops attempting to move the device back onto IMA. */\n ima_supported?: boolean;\n /** Flat alias of `telemetry.inputSource`. Dead on both sides — no producer emits either casing, and the column has no ClickHouse mirror. */\n input_source?: string;\n /** Numeric IMA `AdError` code from the last IMA failure. The browser SDK sends it inside `ima`, which outranks this flat key; it is persisted on the device record and read by nothing. */\n last_ima_error_code?: number;\n /** Numeric VAST error code from the last tag failure (e.g. 303 for an empty wrapper chain), sent inside `ima` by the browser SDK. Persisted only, with no server-side consumer. */\n last_vast_error_code?: number;\n /** Timestamp of the last outbound VAST request. No producer exists anywhere in the repo and nothing reads the stored value, so this field is dead on both ends. */\n last_vast_request_at?: string;\n /** Timestamp of the last VAST response received. Like `last_vast_request_at`, no agent or SDK emits it and nothing consumes it. */\n last_vast_response_at?: string;\n /** Current GPS fix for a moving screen. Processed only for screens whose `mobility_type` is `mobile` or `semi_mobile` (declare it at registration or via PATCH /device/{deviceId}), OR for any screen still missing coordinates (a one-time self-heal that also kicks off venue classification). Static screens with valid coordinates ignore it. Updates live geo-targeting, geofencing and analytics. Fully optional — omit it on stationary screens. */\n location?: {\n /** Horizontal accuracy radius in meters, stored as the device reports it. */\n accuracy_m?: number;\n /** Up to 200 fixes recorded while the device was offline, replayed as a historical track. Timestamps MUST be in ascending order — a non-chronological batch is rejected with HTTP 400. Only the top-level `location` updates the live serving position. */\n buffered_locations?: Array<{\n accuracy_m?: number;\n heading?: number;\n lat: number;\n lng: number;\n speed_mps?: number;\n timestamp?: number;\n }>;\n /** Bearing in degrees (0–360, 0 = true north). Omit if unavailable. */\n heading?: number;\n /** Latitude in decimal degrees (WGS 84). */\n lat?: number;\n /** Longitude in decimal degrees (WGS 84). */\n lng?: number;\n /** `gps` — a live on-device fix. `gps_buffered` — a fix recorded while offline and replayed on reconnect. */\n source?: \"gps\" | \"gps_buffered\";\n /** Ground speed in meters per second. Omit if unavailable. */\n speed_mps?: number;\n /** Unix epoch milliseconds of the on-device fix. Send the device clock value. A fix whose timestamp is more than 60 s ahead of the server's clock, or more than 6 h behind it, is kept without a fix time. A position jump implying > 320 km/h between consecutive fixes is rejected. */\n timestamp?: number;\n };\n /** Alternate namespace for telemetry keys (legacy support). */\n metadata?: Record<string, unknown>;\n /** Flat alias of `telemetry.muted`. The TV agents fall back to `false` here when power state is unreadable while falling back to `null` for `brightness`/`volume`, so a `false` cannot be read as evidence the read succeeded. */\n muted?: boolean;\n /** Connection metrics. The server reads ONLY the camelCase keys shown here; the shipped `trillboards-lite` browser SDK sends this block snake_case (`connection_type`, `effective_type`, `bandwidth_mbps`, `latency_rtt`), so not one key matches and its entire network block is silently discarded on every heartbeat. `bandwidthMbps`/`downlinkMbps` share one slot and `latencyRtt`/`rtt` share another. */\n network?: {\n bandwidthMbps?: number;\n connectionType?: string;\n downlinkMbps?: number;\n effectiveType?: string;\n latencyRtt?: number;\n rtt?: number;\n saveData?: boolean;\n };\n /** Flat alias of `telemetry.networkStrengthDbm`, in dBm (negative). No agent or SDK populates either spelling, so it is absent fleet-wide. */\n network_strength_dbm?: number;\n /** Flat alias of `telemetry.powerState`, used only when the nested value is absent. It coerces to `unknown` rather than failing, and `off`/`standby` downgrade the screen to `standby` so it stops receiving ads. No shipped producer uses this snake_case spelling — every emitter sends the camelCase form. */\n power_state?: \"on\" | \"standby\" | \"off\" | \"unknown\";\n /** Declares that the player emits proof-of-play. No producer sends it and no server logic reads it — proof-of-play eligibility is decided server-side, not from this flag. */\n proof_of_play_enabled?: boolean;\n /** Lowest-precedence source for the IMA/build fields: `ima_supported`, `ima_integration`, `ima_sdk_version`, `android_api_level` and `webview_chromium_major` are read from here only after `ima.*`, `telemetry`/`metadata` and the flat body keys have all missed. Its `version` sub-key is NOT read at all — the SDK version the server stores comes from body `agent_version`/`sdk_version`. */\n sdk?: {\n android_api_level?: number;\n desugaring_enabled?: boolean;\n ima_integration?: \"android_native\" | \"html5_webview\" | \"disabled\" | \"unknown\";\n ima_sdk_version?: string;\n ima_supported?: boolean;\n version?: string;\n webview_chromium_major?: number;\n };\n /** Third-precedence alias of `agent_version` — same destination and the same BYO-nulling rule. This is what the shipped `trillboards-lite` browser SDK sends, alongside `sdk_type: \"lite\"`, which is itself one of the signals that classify the heartbeat as ours. */\n sdk_version?: string;\n telemetry?: PartnerHeartbeatTelemetry;\n /** Flat alias of `telemetry.temperatureC`, in degrees Celsius from the Android battery thermal sensor. No producer uses this snake_case spelling, and battery-less CTV hardware omits the reading altogether rather than sending 0. */\n temperature_c?: number;\n /** Flat alias of `telemetry.uptimeSeconds`. It measures seconds since DEVICE boot (`SystemClock.elapsedRealtime()/1000` on Android — it survives an agent restart and counts through deep sleep), not agent process uptime. Producers send the camelCase form inside `telemetry`/`metadata`. */\n uptime_seconds?: number;\n /** Flat alias of `telemetry.volume`, on the platform's own scale (0-100 on Tizen/webOS), not a percentage of any documented maximum. Emitted by the Tizen and webOS agents through `metadata` only. */\n volume?: number;\n /** Chromium major version behind the WebView, parsed from the `Chrome/<n>` user-agent token. Below 87 and below 80 each append an advisory note to `ad_delivery_profile.notes`; neither changes the delivery mode. */\n webview_chromium_major?: number;\n};\n\nexport type PartnerHeartbeatResponse = {\n data?: PartnerHeartbeatData;\n /** Always `true` on this 200 response. The failure paths — 404 for an unresolvable `deviceId`, 400 for an invalid BLE payload, 500 on an internal error — return `success: false` with a `message` and no `data` key at all. */\n success?: boolean;\n};\n\nexport type PartnerHeartbeatTelemetry = {\n /** A status word whose vocabulary depends on which SDK reports it: the Android sensing SDK derives it from camera health and sends exactly `sensing` (camera open), `camera_failed`, `starting` or `idle`, while the browser SDK sends `foreground`/`background` from `document.hidden`. Those four Android values are also an in-band signal that the heartbeat came from a Trillboards SDK, which is what allows `agentVersion` to be stored. */\n agentStatus?: string;\n /** Version of the TRILLBOARDS agent/SDK only. If the heartbeat does not self-identify as one of ours (`ima.integration=html5_webview`, `sdk_type=lite`, a `capabilities.audienceSensing.cameraHealth` block, or one of the four sensing `agentStatus` values), the server discards what you send and force-nulls both casings, so a BYO player's own app version is never stamped as ours. Resolution order is `telemetry.agentVersion`, then body `agent_version`, then body `sdk_version`. */\n agentVersion?: string;\n /** Android SDK/API level (23 = Marshmallow, 28 = Pie); below 23 it forces `vast_fallback` when `ima_integration` is `android_native`. Beware the unit — the shipped browser SDK derives it from the user-agent token `Android <n>`, which is the platform RELEASE number (Android 9 gives 9), not the API level (28). */\n android_api_level?: number;\n /** Downlink bandwidth in MEGABITS PER SECOND from `navigator.connection.downlink`. It shares one destination with `downlinkMbps` (`bandwidthMbps` wins), then the same two keys inside `network`, so only one value ever survives. */\n bandwidthMbps?: number;\n /** Panel backlight level on the platform's own scale — on Samsung Tizen and LG webOS the 0-100 picture-brightness value, `null` when that picture-control service is unavailable (VIDAA hardcodes `null`, the API does not exist there). Neither partner SDK sends it; the `brightness` seen in the Android agents is a command-ACK detail, not heartbeat telemetry. */\n brightness?: number;\n /** Free-text connection label (`wifi`, `ethernet`, `cellular`…) that becomes `networkMetrics.connectionType`; `networkConnectionType` outranks it and `network.connectionType` is the fallback. It does NOT set the OpenRTB `device.connectiontype` sent in bid requests — that is the separate all-lowercase `connectiontype` key, which is an INTEGER enum (0 unknown, 1 ethernet, 2 wifi, 3 cellular, 4 2G, 5 3G, 6 4G, 7 5G), is not declared in this schema, and only passes because the object allows additional properties. */\n connectionType?: string;\n /** Whether Java 8+ library desugaring is enabled in a native-Android IMA host. No agent or SDK anywhere in the repo emits it, so it is absent fleet-wide; it is consulted only when `ima_integration` is `android_native` and `android_api_level` is 25 or below, where `false` adds a known-crash-risk note to `ad_delivery_profile.notes`. */\n desugaring_enabled?: boolean;\n /** Second-precedence spelling of `bandwidthMbps` (Mbps) filling the same `networkMetrics.bandwidthMbps` slot; ignored whenever `bandwidthMbps` is also present. The npm SDK sends both from the same `connection.downlink` reading. */\n downlinkMbps?: number;\n /** How the player reaches Google IMA. `html5_webview` is hardcoded by both Trillboards browser SDKs and is itself the signal marking the heartbeat as ours (which is what allows `agentVersion` to be stored); `android_native` with `android_api_level` below 23 forces `ad_delivery_profile.mode` to `vast_fallback`. Absent or unrecognised values normalise to `unknown`, never null, and the undocumented nested `ima.integration` key — what the shipped browser SDK actually sends — outranks this flat one. */\n ima_integration?: \"android_native\" | \"html5_webview\" | \"disabled\" | \"unknown\";\n /** The IMA SDK's own version string. Nothing produces this flat spelling — the shipped browser SDK sends it as `ima.sdk_version`, read from `window.google.ima.VERSION`, which takes precedence — and it is `null` whenever IMA failed to load, so absence usually pairs with `ima_supported: false`. */\n ima_sdk_version?: string;\n /** Free-text explanation for the `ima_supported` verdict — the browser SDK sends the literal `ok` when IMA loaded and its load-failure reason otherwise, under `ima.support_reason`, which outranks this flat spelling (nothing produces the flat one). Stored only: the ad-delivery decision never reads it. */\n ima_support_reason?: string;\n /** Whether the IMA SDK is usable on this player. An explicit `false` is a hard switch, not a hint: it pins `ad_delivery_profile.mode` to `vast_fallback` AND sets `auto_recovery_enabled: false`, so the server stops trying to move the device back onto IMA. The browser SDKs compute it as `Boolean(window.google.ima)` / an IMA load-failure flag. */\n ima_supported?: boolean;\n /** Free-form active-input label (e.g. an HDMI port name), stored verbatim on the device record. It has NO producer: no agent or SDK in the repo emits either casing, the only other references are Flutter UI localisation strings, and the schema sidecar marks the column PG-only with no ClickHouse mirror. Treat it as permanently absent. */\n inputSource?: string;\n /** Numeric IMA `AdError` code from the most recent IMA failure, cached client-side between heartbeats. The shipped browser SDK sends it inside `ima`, which outranks this flat key. Persisted on the device record only — no server logic reads it. */\n last_ima_error_code?: number;\n /** Numeric VAST error code from the most recent tag failure (e.g. 303 for a wrapper chain that resolved to nothing), cached client-side and sent inside `ima` by the browser SDK. Persisted only, with no server-side consumer. */\n last_vast_error_code?: number;\n /** Timestamp of the player's last outbound VAST request. It has NO producer: a repo-wide grep finds only the server-side reader and the generated API client, so it is absent on every live device, and nothing downstream consumes the stored value. */\n last_vast_request_at?: string;\n /** Timestamp of the last VAST response the player received. Like `last_vast_request_at` it has no producer in any agent or SDK, and the persisted value has no consumer. */\n last_vast_response_at?: string;\n /** Round-trip time in MILLISECONDS from `navigator.connection.rtt`. Shares one destination with `rtt` (`latencyRtt` wins); the read-side schema bounds the stored value to 0-60000. */\n latencyRtt?: number;\n /** Device manufacturer. Written to the device column but not authoritative — every heartbeat runs a device-identity fusion pass that can replace `make`/`model`/`os` from the user agent and 51Degrees specs, and the corrected value is what is persisted and read back. */\n make?: string;\n /** Device model, subject to the same identity-fusion overwrite as `make`. The browser SDK has no real model string and sends the full user agent in this field instead. */\n model?: string;\n /** Audio mute state. Note the asymmetry with `brightness`/`volume`: when the Tizen/webOS agent cannot read power state it still sends `false` here while sending `null` for the other two, so `false` does not distinguish \"not muted\" from \"could not be read\". The `muted` the browser SDK sends elsewhere belongs to the ad request, not the heartbeat. */\n muted?: boolean;\n /** Highest-precedence spelling of the free-text connection label that becomes `networkMetrics.connectionType`. No agent or SDK emits it, so in practice the value comes from `connectionType` or `network.connectionType`. Sending any of the six network keys makes the server stamp `networkMetrics.measuredAt` and increment `networkMetrics.measurementCount`. */\n networkConnectionType?: string;\n /** Highest-precedence spelling of the Network Information API `effectiveType` bucket (`slow-2g`/`2g`/`3g`/`4g`) — a round-trip-time and downlink classification, not the physical bearer. No producer emits this spelling; live values arrive as `effectiveType` or `network.effectiveType`. */\n networkEffectiveType?: string;\n /** Wi-Fi/cellular RSSI in dBm (negative; the read-side schema bounds it to -120..0). No agent or SDK populates it — the field exists on the Kotlin model and the only value in the tree is a hardcoded test literal — so it is absent fleet-wide. Per-AP signal is instead carried by `nearbyWifiNetworks[].signalStrengthDbm` and `wifiEnvironment.connectedSignalDbm`. */\n networkStrengthDbm?: number;\n /** Operating-system family, written straight to the device column `os` — the Android agents send the full `\"Android <release>\"` string, not a bare family name. It is an input to the identity-fusion pass that runs on every heartbeat, so what the read endpoint later returns may be a corrected value rather than what you sent. */\n os?: string;\n /** Lower-precedence alias of `osv`: both resolve to the same normalized value and the same `osv` device column, with `osv` winning when both are present. */\n osVersion?: string;\n /** OS version string. `osv` and `osVersion` are two spellings of ONE value — the normalizer takes `osv` first and falls back to `osVersion`, and both land in the single device column `osv`, so sending different values in the two keys silently keeps only `osv`. */\n osv?: string;\n /** Display power as the device reports it. Never null and never rejected: any unrecognised or absent value is coerced to `unknown`, and `off`/`standby` make the server record the screen `standby` rather than `online`, which stops ad delivery. Producers use this camelCase spelling only — the browser SDK derives it from `document.hidden` (`standby` when hidden, else `on`), the Fire TV / Android TV agents send their `PowerController` state; the flat `power_state` alias has no producer at all. */\n powerState?: \"on\" | \"standby\" | \"off\" | \"unknown\";\n /** Whether the player is configured to emit proof-of-play. No agent or SDK sends it, so it is absent fleet-wide, and nothing reads the stored value — actual proof-of-play eligibility is decided server-side, never from this flag. */\n proof_of_play_enabled?: boolean;\n /** Second-precedence spelling of `latencyRtt` (milliseconds), ignored when `latencyRtt` is also present. */\n rtt?: number;\n /** The browser's Data Saver preference (`navigator.connection.saveData`). Accepted as a boolean or the strings `\"true\"`/`\"false\"`; anything else becomes `null` rather than `false`. */\n saveData?: boolean;\n /** Degrees Celsius from the BATTERY thermal sensor — the only real °C reading a non-system Android app can obtain. Battery-less CTV hardware (Fire TV sticks, TV boxes) omits the key entirely rather than sending 0, so absence means \"no battery sensor\", not \"cool\"; the derived ClickHouse `cpu_temp_c` was measured above zero on ZERO rows. */\n temperatureC?: number;\n /** Seconds since the DEVICE booted, not since the agent process started: the Android producers send `SystemClock.elapsedRealtime() / 1000`, which keeps counting through deep sleep and resets only on reboot, so an agent crash-loop leaves this climbing. It is also the sole camelCase survivor in the ClickHouse heartbeat blob — every other gauge there is snake_case. */\n uptimeSeconds?: number;\n /** Speaker volume on the platform's own scale — 0-100 on Tizen (`tizen.tvaudiocontrol.getVolume()`) and webOS, `null` when the audio-control service is unavailable or (on VIDAA) does not exist. It is not normalised to a percentage of any maximum, and neither partner SDK sends it. */\n volume?: number;\n /** Major version of the Chromium engine backing the WebView, parsed from the `Chrome/<n>` user-agent token by both browser SDKs. Below 87 adds a content-restoration warning to `ad_delivery_profile.notes` and below 80 an autoplay/media warning; neither changes the delivery mode. */\n webview_chromium_major?: number;\n [key: string]: unknown;\n};\n\n/**\n * What a rider did with the screen, reported by the partner SDK's reportInteraction. (partner, device, interaction_id) is the event's identity.\n */\nexport type PartnerInteraction = {\n /** The creative on screen, as the host names it. */\n creative_id?: string;\n /** The host app's id for this interaction (idempotency key, per device). */\n interaction_id: string;\n /** When it happened (device clock), within the last 7 days and at most 5 minutes ahead of the server clock. */\n occurred_at: string;\n /** Milliseconds into the creative's playback when it happened. */\n t_ms?: number;\n /** What happened. seatbelt_reminder_shown = the host app showed a SeatbeltReminder that agent-core 1.24 delivered to its safety-event listener; send the reminder's eventId as interaction_id. Never a programmatic ad-lifecycle event (those are POST /device/{deviceId}/programmatic-event). */\n type: \"tap\" | \"qr_shown\" | \"qr_scanned\" | \"dwell_prompt\" | \"seatbelt_reminder_shown\";\n};\n\nexport type PartnerLBarContent = {\n /** The venue name rendered in the L-bar; empty string when unset. One of only three scalar keys (`enabled`, `business_name`, `current_promotion`) the PUT copies straight across — every other key here is an object that gets shallow-merged. */\n business_name?: string;\n /** Holds `phone`, `email`, `website`, `address` and `wifi_password`, all free text and all empty-string when unset, shallow-merged on PUT. `wifi_password` is rendered on the screen, so treat it as public. */\n contact_info?: Record<string, unknown>;\n /** The promo line shown in the L-bar. It is also writable through the much lighter `PUT .../l-bar/live-updates` endpoint as its `promotion` field, which is the path to use for frequent changes. */\n current_promotion?: string;\n /** Free-text lines rotated in the L-bar. Unlike every nested object above, this is REPLACED wholesale whenever it is present in the body, so send the complete list each time. */\n custom_messages?: Array<string>;\n /** The L-bar's own cosmetics — NOT the screen display-settings resource. Holds `background_color` (`#000000`), `text_color` (`#FFFFFF`), `accent_color` (`#4A90E2`), the widget toggles `show_weather`/`show_time`/`show_date`/`show_store_hours` (default true) and `show_contact_info`/`show_social_media`/`show_sports_scores`/`show_stock_market` (default false), plus `selected_sports_leagues`. Shallow-merged on PUT. */\n display_settings?: Record<string, unknown>;\n /** Whether the L-bar content region is drawn; defaults `false`. It is COUPLED to `display_preferences.display_mode` on the PUT — sending `display_mode: \"fullscreen\"` forces this to `false`, and `\"l-bar\"` forces it to `true` unless you send an explicit boolean in the same request. */\n enabled?: boolean;\n /** Exactly three sub-objects are merged — `queue_management` (`enabled`, `current_number`, `queue_message`, default `Now Serving`), `wait_time` (`enabled`, `current_wait_minutes`, `wait_message`, default `Current Wait Time`) and `custom_status` (`enabled`, `status_message`); any other key nested here is silently dropped. The live-updates endpoint sets `enabled` for you as a side effect of writing a value, and clears `custom_status.enabled` when you send an empty message. */\n live_updates?: Record<string, unknown>;\n /** Holds `facebook`, `instagram`, `twitter` and `google_reviews`, free text and empty-string when unset, shallow-merged on PUT. */\n social_media?: Record<string, unknown>;\n /** Free-text opening hours: one string per lowercase day name (`monday`..`sunday`) plus a `timezone` defaulting to `America/New_York`. The strings are displayed verbatim — nothing parses them into open/close times — and the PUT shallow-merges them, so you can send a single day. */\n store_hours?: Record<string, unknown>;\n};\n\nexport type PartnerLBarDisplayPreferences = {\n /** Which surfaces ads may occupy; `null` by default, in which case it is derived from `display_mode` + `l_bar_ads_enabled`. Setting `lbar` also flips `display_mode` to `l-bar` when the same request does not set it, and narrows the fullscreen surface to `self_promo` only — paid and programmatic demand stops running full screen. */\n ad_surface_mode?: \"fullscreen\" | \"lbar\" | \"pip\" | \"all\" | null | null;\n /** Default `fullscreen`. Writing it has side effects: `fullscreen` also forces `l_bar_ads_enabled` and `l_bar_content.enabled` to `false`, and `l-bar` forces both to `true` unless the same request sets them explicitly. A change also emits a `refresh` control event so the kiosk reloads and swaps surfaces. */\n display_mode?: \"fullscreen\" | \"l-bar\";\n /** Seconds one L-bar ad is held; default 8, accepted range 3-30. It must be strictly LESS than `l_bar_ad_interval`, and the check falls back to the stored interval when this request does not send one. */\n l_bar_ad_duration?: number;\n /** Seconds between L-bar ad plays; default 25, accepted range 10-120. It must be strictly GREATER than `l_bar_ad_duration`, and the pair is cross-checked against the STORED values, so lowering this alone can fail on a duration set by an earlier request. */\n l_bar_ad_interval?: number;\n /** Whether ads may fill the L-bar slots. When it has never been set it is DERIVED as `display_mode === \"l-bar\"`, so it reads `true` on an L-bar screen and `false` on a fullscreen one. Accepts a boolean or the strings `\"true\"`/`\"false\"`; anything else is 400. */\n l_bar_ads_enabled?: boolean;\n /** Which edge the L-bar occupies; default `bottom`. It decides ad-slot geometry — `bottom` lays slots out horizontally and `right` vertically — and the slot COUNT is derived from it, so switching sides changes how many `lbar_slot_*` surfaces exist. This endpoint does not validate the value against any enum (the display-settings resource's `l_bar.position` does, and accepts `top`/`bottom`/`left`/`right`). */\n l_bar_position?: \"bottom\" | \"right\";\n /** Share of the screen the L-bar occupies, as a PERCENT; default 20. This endpoint accepts it with no range check at all, while the equivalent `l_bar.size_percent` on the display-settings resource clamps to 10-50. It is also not what sizes ad slots — `buildAdSurfaces` uses a fixed 25%. */\n l_bar_size?: number;\n /** Picture-in-picture ad window: `enabled` (default false), `position` (default `bottom-right`), `size_ratio` as a FRACTION of the screen 0.15-0.45 (default 0.3), `aspect_ratio` as width/height 0.5-3 (default 16/9), and `margin` in PIXELS 8-64 (default 24). A PiP surface is only produced when `ad_surface_mode` is `pip` or `all` AND `enabled` is true. */\n pip_settings?: {\n aspect_ratio?: number;\n enabled?: boolean;\n margin?: number;\n position?: \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\n size_ratio?: number;\n };\n};\n\nexport type PartnerLBarSettings = {\n /** The resolved renderable ad surfaces, one entry per surface (`fullscreen`, `lbar_slot_1..N`, `pip`). Present ONLY on the PUT response; the GET does not compute it. */\n ad_surfaces?: Array<{\n allowed_ad_types?: Array<string>;\n dimensions?: {\n height?: number;\n width?: number;\n };\n display_area_ratio?: number;\n display_mode?: string;\n enabled?: boolean;\n id?: string;\n surface_group?: string;\n surface_type?: string;\n }>;\n display_preferences?: PartnerLBarDisplayPreferences;\n l_bar_content?: PartnerLBarContent;\n /** The screen's resolved pacing contract, present only on the PUT response. `policy_version` is `v1` and every interval is in MILLISECONDS (`paid_interval_ms`, `self_promo_interval_ms`, `image_ad_total_ms`, `image_ad_min_ms`); it also carries `max_paid_share` (null unless a loop share cap is enabled), `surface_policy`, `schedule_policy`, `pip_settings` and `lbar_settings`. */\n playback_policy?: Record<string, unknown>;\n};\n\n/**\n * Where the screen's location comes from, and, per rail, whether that rail\n * has the location it needs. Location is a readiness capability, never a\n * registration rule: a device without one is registered, and this says\n * which rails serve it until a position arrives. `monetization_status` is\n * computed over `inventory_feed` and `geo_targeted_demand`.\n *\n */\nexport type PartnerLocationCapability = {\n /** `precise` — the screen or its device has coordinates; `fleet_market` — none of its own, your account's declared fleet market location lists it; `none`. */\n position?: \"precise\" | \"fleet_market\" | \"none\";\n rails?: {\n geo_targeted_demand?: PartnerLocationRail;\n inventory_feed?: PartnerLocationRail;\n visit_attribution?: PartnerLocationRail;\n };\n};\n\nexport type PartnerLocationRail = {\n /** Where a ready rail's location comes from; null when blocked. Only `inventory_feed` reads `fleet_market`. */\n location_source?: \"screen\" | \"device\" | \"fleet_market\" | null | null;\n /** Why a blocked rail has no location; null when ready. See https://api.trillboards.com/docs/monetization-requirements#location-capability. */\n reason?: \"awaiting_device_gps\" | \"no_location_provided\" | \"city_without_state\" | \"geocode_failed\" | \"screen_position_pending\" | \"country_unknown\" | \"country_not_supported\" | null | null;\n status?: \"ready\" | \"blocked\";\n};\n\n/**\n * Stripe-style monetization requirements (account.requirements analogue).\n * Returned on device register / update / get / batch so bulk partners can\n * detect and self-remediate missing data instead of discovering silent\n * gaps later. `currently_due` blocks or limits monetization now;\n * `eventually_due` improves serving but isn't blocking.\n *\n */\nexport type PartnerMonetizationRequirements = {\n /** Requirements blocking or limiting monetization right now. */\n currently_due?: Array<PartnerRequirement>;\n /** Non-blocking improvements (e.g. display dimensions). */\n eventually_due?: Array<PartnerRequirement>;\n};\n\nexport type PartnerNetworkConfig = {\n /** Partner-level ad pacing and identity controls applied to every screen you own; on the `/ads` path they OUTRANK the screen's own `ad_interval_seconds` and the partner's `sdk_config.max_ads_per_hour`. A PATCH merges key-by-key and rejects unknown keys with a 400, and sending `null` for `identity_mode` or `self_promo_priority_mode` DELETES the key rather than storing null, so those two come back absent rather than as `null`. */\n ad_delivery?: {\n /** `/ads` response cache lifetime. Zero requires a fresh response for every request. */\n cache_max_age_seconds?: number;\n /** Delegates resettable advertising identity to the native Google IMA SDK; the API never fabricates one. Set null to restore the platform default identity path. */\n identity_mode?: \"ima_sdk\" | null;\n /** Fleet-wide hourly ad cap returned in `settings.max_ads_per_hour`. */\n max_ads_per_hour?: number;\n /** Requests non-personalized demand when true. Never provide an advertising identifier unless the OS legitimately exposes it. */\n non_personalized_ads?: boolean;\n /** Returned as both `settings.ad_interval` and `header_bidding_settings.min_interval_seconds`; the player should request each monetizable slot at this cadence. */\n request_interval_seconds?: number;\n /** Controls whether a scheduled self-promo/house slot may lead the VAST waterfall. `scheduled_primary` preserves the bounded house cadence; `fallback_only` keeps self-promo behind paid programmatic demand; `disabled` removes the self-promo/house creative from your screens entirely — it is served on neither `ads[]` nor the VAST waterfall, and no new house creative is generated for your screens. Paid-direct guarantees always retain priority and are never affected. Set null to restore `scheduled_primary`. */\n self_promo_priority_mode?: \"scheduled_primary\" | \"fallback_only\" | \"disabled\" | null;\n };\n /** White-label presentation for your buyer-facing advertiser portal. These values are read and written directly against the branding your portal renders — a GET returns what a buyer currently sees, and a write takes effect on their next page load. Omitted entirely if no white-label portal is linked to your partner account; writing branding in that state returns `409` `white_label_distributor_not_linked` rather than accepting values that would render nowhere. Contact Trillboards to have one linked. */\n branding?: {\n /** Shown as the portal name and the browser tab title. */\n display_name?: string;\n /** Absolute HTTPS URL. */\n favicon_url?: string;\n font_body?: string;\n font_heading?: string;\n /** Absolute HTTPS URL. Rendered in the portal header. */\n logo_url?: string;\n /** CSS color. HSL triplets (\"210 90% 40%\") and hex are both accepted. */\n primary_color?: string;\n secondary_color?: string;\n tagline?: string;\n };\n /** Default content preferences inherited by newly provisioned screens. */\n default_content_preferences?: Record<string, unknown>;\n /** Partner-level (fleet-wide) demand-source ordering; there is no per-screen override for these keys. A PATCH merges key-by-key into what is stored, and any key other than `source_order`/`total_timeout_ms` is rejected with a 400 naming it. The GET omits this object entirely when nothing has been stored, so absence means \"never configured\" — it is not a rendering of the effective defaults. */\n default_waterfall?: {\n /** Preferred opaque source aliases from the `/ads` response. Sources not listed here remain enabled and are appended. Internal demand names and `sort_by: cpm` are rejected. */\n source_order?: Array<string>;\n /** Total deadline for one waterfall snapshot, in milliseconds. */\n total_timeout_ms?: number;\n };\n /** Limits your branded buyer portal to YOUR OWN screens. Off by default: a branded portal otherwise sells the whole Trillboards network, which is what most white-label partners want. The screen set is DERIVED from the media-owner identity your screens already carry — both the ones we host and the ones resold through partner exchanges — so it stays correct as you add inventory and there is no list to maintain. It cannot be set to another operator's inventory. */\n storefront?: {\n /** When true, every inventory surface in your buyers' portal — search, map, forecasts, quotes, bookings and proposals — is limited to your own screens. */\n enabled?: boolean;\n };\n};\n\n/**\n * Partner-safe partial update. Internal `partners`, `supply_chain`, seller\n * IDs, direct demand names, and CPM-sort controls are rejected.\n *\n */\nexport type PartnerNetworkConfigUpdate = PartnerNetworkConfig;\n\n/**\n * A log-level play for a partner-served ad.\n */\nexport type PartnerPlay = {\n /** Optional ad/line identifier; defaults to the sha-keyed creative when creative_ref is sent. */\n ad_id?: string;\n /** ISO 8601 timestamp when the play completed. */\n completed_at: string;\n /** Optional. The classification reference from POST /creatives — `sha256:<hex>` or the bare 64-char content hash. Omit it for a play of an ad you hold no content hash for; the play is then recorded with its `ad_id`, or with none. */\n creative_ref?: string;\n /** Play duration in seconds (default 30). */\n duration_seconds?: number;\n /** Your unique id for this play (idempotency key). */\n play_id: string;\n /** Optional partner request identifier. */\n request_id?: string;\n /** One of your registered earner-screen ids. */\n screen_id: string;\n};\n\n/**\n * Partner-reported impression and revenue counts for reconciliation against deduped completed_impressions. Ingested from VidVerto SSP API and Adipolo API / JustBaat CSV reports. PG only.\n */\nexport type PartnerReconciliationReports = {\n adjustment_id?: number | null;\n applied_at?: string | null;\n /** applied_report_hash/applied_at (mig 449) gate the hourly pricing re-apply: syncs skip the CI re-stamp + PED rebuild when the applied tuple is unchanged. Written ONLY by partnerPricingApplyGate.reco... */\n applied_report_hash?: string | null;\n created_at: string;\n /** Wave 2 B added demand_partner_id INT + variant_id INT FKs. Legacy partner_key TEXT kept as wire-format alternate-key. Backfill via follow-up Wave 2 B-backfill PR. */\n demand_partner_id?: number | null;\n /** VidVerto synced daily via vidvertoRevenueSync before programmatic earnings aggregation. */\n id?: number | null;\n imported_by?: string | null;\n /** NUMERIC(8,4). */\n impression_variance?: number | null;\n /** NUMERIC(10,4). */\n our_ecpm?: number | null;\n our_impressions?: number | null;\n our_requests?: number | null;\n /** NUMERIC(14,6). */\n our_revenue?: number | null;\n provider_name: string;\n /** Adipolo publisher revenue requires a completed vast_requests lifecycle plus latest screens_live source='owned'. ID shape/event alone is insufficient; shared, external, and unjoined bundles are excl... */\n raw_data?: Record<string, unknown> | null;\n report_date: string;\n report_type: \"partner_api\" | \"manual_upload\" | \"email_extract\" | \"dashboard_snapshot\";\n /** NUMERIC(10,4). */\n request_variance?: number | null;\n resolution_notes?: string | null;\n /** NUMERIC(8,4). */\n revenue_variance?: number | null;\n reviewed_at?: string | null;\n reviewed_by?: string | null;\n status: \"pending\" | \"matched\" | \"flagged\" | \"disputed\" | \"resolved\" | \"accepted\";\n /** NUMERIC(10,4). */\n their_ecpm?: number | null;\n /** NUMERIC(6,4). */\n their_fill_rate?: number | null;\n their_impressions?: number | null;\n their_requests?: number | null;\n /** NUMERIC(14,6). */\n their_revenue?: number | null;\n updated_at: string;\n variant_id?: number | null;\n};\n\n/**\n * Optional display behavior applied while registering or re-registering a device.\n */\nexport type PartnerRegistrationDisplaySettings = {\n content_fit?: ContentFitSettings;\n};\n\nexport type PartnerRequirement = {\n /** Stable machine code, e.g. location_unresolvable, location_awaiting_device_gps, enrichment_failed, display_size_missing. */\n code?: string;\n /** Built mechanically as `https://api.trillboards.com/docs/monetization-requirements#<code>`, so the anchor always matches `code`. A bare `docs.` subdomain is deliberately NOT used — it resolves to an unrelated portal SPA rather than documentation — and a CI guard blocks reintroducing it. */\n doc_url?: string;\n /** The request field this concerns (location, venue, display). */\n field?: string;\n /** Human-readable, actionable remediation. */\n message?: string;\n /** Specific reason variant, e.g. city_without_state, no_location_provided, geocode_failed, awaiting_device_gps. */\n reason?: string;\n};\n\n/**\n * Partner-safe subset of device telemetry. Strips MDM compliance,\n * Vertex embeddings, identity-correction internals, partner identifiers,\n * and Mongo `_id` so partners only see what they need to decide\n * whether a device is healthy enough to receive ads.\n *\n */\nexport type PartnerSafeHeartbeatTelemetry = {\n /** Agent lifecycle word, with two disjoint vocabularies depending on the reporting SDK (`sensing`/`camera_failed`/`starting`/`idle` from the Android sensing SDK, `foreground`/`background` from the browser SDK). Absent on the primary `redis_twin` path — the device twin has no such field, so it surfaces only on the `pg_fallback` path. */\n agentStatus?: string | null;\n /** Trillboards agent/SDK version. Never present here on either path: the heartbeat writes it to the device record root, not into the last-heartbeat telemetry blob, and the device twin does not carry it either. It is also nulled at write time for any heartbeat that does not self-identify as a Trillboards SDK. */\n agentVersion?: string | null;\n /** Downlink bandwidth in Mbps. Never present on either path: the write path stores it under `networkMetrics.bandwidthMbps`, a sibling of the last-heartbeat blob this endpoint reads, and the device twin has no such field. */\n bandwidthMbps?: number | null;\n /** Panel brightness on the platform's own scale (0-100 on Tizen/webOS, `null` where no picture-control API exists). Absent on the primary `redis_twin` path — the device twin has no such field, so it can only appear on the `pg_fallback` path and only for the TV agents that emit it. */\n brightness?: number | null;\n /** Free-text connection label. Not present on either path — the write path stores it as `networkMetrics.connectionType`, and while the device twin does carry a `connectiontype`, that is the all-lowercase OpenRTB INTEGER enum, a different quantity under a different key. */\n connectionType?: string | null;\n /** Running count of back-to-back ad failures the player has seen, from the heartbeat's `playback.consecutive_failures`; it is what drives the server's stalled-playback recovery. Absent on the primary `redis_twin` path — the device twin has no such field, so it appears only on the `pg_fallback` path. */\n consecutiveAdFailures?: number | null;\n /** Network Information API `effectiveType` bucket (`slow-2g`/`2g`/`3g`/`4g`) — a latency/throughput classification, not the physical bearer. Stored under `networkMetrics.effectiveType` and carried by neither source object, so it never appears here. */\n effectiveType?: string | null;\n /** Round-trip time in MILLISECONDS (bounded 0-60000 by the read-side schema). Like `bandwidthMbps` it is stored under `networkMetrics.*` and is on neither of this endpoint's two source objects. */\n latencyRtt?: number | null;\n /** Device manufacturer, after the identity-fusion correction pass — so it can differ from what the device reported. Present only on the `redis_twin` path: it is a device-record field, and the `pg_fallback` path returns just the last-heartbeat telemetry blob. */\n make?: string | null;\n /** Device model, after identity-fusion correction. Like `make`, it is a twin-only field and is absent whenever `measurementMetadata.source` is `pg_fallback`. */\n model?: string | null;\n /** Mute state. Absent on the primary `redis_twin` path — the device twin has no such field. On the agents that do report it, `false` is also the fallback used when the power state could not be read at all, so it does not prove a successful read. */\n muted?: boolean | null;\n /** RSSI in dBm, bounded -120..0. Doubly absent: no shipped agent produces it in the first place, and absent on the primary `redis_twin` path — the device twin has no such field. */\n networkStrengthDbm?: number | null;\n /** Declared but NEVER POPULATED on either source path: nothing writes an `onlineStatus` into the last-heartbeat telemetry blob and the device twin has no such field. The equivalent state lives on the screen row (`earner_screens.online_status`), which this endpoint does not read — use `stalenessSeconds` to judge liveness instead. */\n onlineStatus?: \"online\" | \"offline\" | \"standby\" | \"unknown\" | null;\n /** Operating-system family after correction — the Android agents report the full `\"Android <release>\"` string. Twin-only, so absent on the `pg_fallback` path. */\n os?: string | null;\n /** OS version string after correction; the heartbeat collapses the `osv` and `osVersion` request spellings into this one value. Twin-only, so absent on the `pg_fallback` path. */\n osv?: string | null;\n /** Last reported display power (`on`/`standby`/`off`/`unknown`). Absent on the primary `redis_twin` path — the device twin has no such field, so it appears only when `measurementMetadata.source` is `pg_fallback`, which reads the persisted last-heartbeat telemetry blob. */\n powerState?: string | null;\n /** Degrees Celsius from the Android BATTERY thermal sensor (bounded -50..150 by the read schema). Absent on the primary `redis_twin` path — the device twin has no such field, and battery-less CTV hardware never reports it at all, so absence carries no thermal information. */\n temperatureC?: number | null;\n /** User agent, resolved with a three-tier fallback — the corrected device UA, then the stored device UA, then the `User-Agent` header of the heartbeat request that built the twin — so it can reflect the HTTP client rather than the player. Twin-only. */\n ua?: string | null;\n /** Seconds since the DEVICE booted, not since the agent started — it survives an agent restart and counts through deep sleep. Absent on the primary `redis_twin` path — the device twin has no such field, so it surfaces only on the `pg_fallback` path. */\n uptimeSeconds?: number | null;\n /** The browser `document.visibilityState` of the player page (`visible`/`hidden`), sourced from the heartbeat's `content.visibility_state`. One of only two telemetry-derived fields that survive onto the Redis device twin, so it is the one health signal available on BOTH source paths. */\n visibilityState?: string | null;\n /** Speaker volume on the platform's own scale, not a normalised percentage. Absent on the primary `redis_twin` path — the device twin has no such field; only the Tizen and webOS agents ever supply it. */\n volume?: number | null;\n [key: string]: unknown;\n};\n\nexport type PartnerSelfPromoAd = {\n /** The 24-hex advertisement id, minted server-side with `newObjectId()`, and the `ad_id` the schedule and disable endpoints take. Partner self-promo rows carry no `user_mongo_id`, so this id is only resolvable through the partner's own `partner-self-promo` placements — an owner-keyed lookup structurally cannot find it. */\n _id?: string;\n /** Click-through / QR destination; empty string, not null, when it was never set — the PG read coalesces it the same way. */\n campaign_link?: string;\n /** When the advertisement row was written. It is the rotation order for a screen's self-promo pool: the placements read sorts `instant_ad_qr` first, then oldest `created_at` first. */\n createdAt?: string;\n /** Defaults to `name` at create, never null. Note the placements listing trims the embedded advertisement to `_id`, `name`, `video`, `photos`, `status` — this field is absent there. */\n description?: string;\n /** Required at create. It is also copied onto each placement as `advertisement_name`. */\n name?: string;\n /** Image creatives. A JSON create puts `image_url` here as the single element; the multipart route puts the uploaded file here when its MIME type is `image/*`. Empty array for a video-only promo. */\n photos?: Array<string>;\n /** Minted as `approved` — self-promo skips review entirely. The disable endpoint sets it to `deactivated`, and that is the ONLY thing that stops delivery: placements stay `booked` so the disable is idempotent, and every serve-path read has to gate on this field. The listings additionally hide `deleted` and `rejected`. */\n status?: string;\n /** URL of the video creative, or `null` for an image-only promo. This is the field the `trillboards_direct` VAST bridge serves from, which is why the advertisement row is written and awaited BEFORE any placement is created. */\n video?: string;\n /** Measured length of the `video` creative in whole seconds, probed when the ad is created. This is the real media length and is what the served VAST declares as `<Duration>`. `null` means the media could not be measured, in which case the VAST declares the 30 second ad-slot default instead. */\n video_duration_seconds?: number | null;\n};\n\nexport type PartnerSelfPromoCreate = {\n /** Click-through / QR destination. Stored as an empty string, not null, when omitted. */\n campaign_link?: string;\n /** Optional; defaults to `name` rather than staying null. */\n description?: string;\n /** Last day the promo may serve, in each screen's own timezone. Omitting it leaves the placement OPEN-ENDED (`preserveOpenEnd: true`) — it runs until you disable the ad — and leaves `end_time` null rather than 23:59. */\n end_date?: string;\n /** The image creative, stored as a single-element `photos` array. At least one of `image_url` or `video_url` is required (400 `Provide image_url or video_url`); sending both stores both. */\n image_url?: string;\n /** Required — the create is rejected 400 without it. It is also written onto every placement as `advertisement_name`, so it is what appears in delivery bookkeeping. */\n name: string;\n /** Which of YOUR screens the promo is placed on. Accepts a real array, a JSON-encoded array string, or a comma-separated string; the list is intersected with the partner's own non-deactivated screens so a foreign id is dropped silently, but if you send ids and NONE survive the create fails 404. Omitting it entirely creates the advertisement with no placements, so it serves nowhere until you call the schedule endpoint. */\n screen_ids?: Array<string>;\n /** First day the promo may serve, resolved PER SCREEN in that screen's own timezone. A window that is already open lands the placement `active` and fires an immediate `self_promo_active` push to the screen; a future one lands it `scheduled`. Serving times are fixed at 00:00-23:59 local and are not derived from this value. */\n start_date?: string;\n /** The video creative. Its length is PROBED at create time and returned as `video_duration_seconds`, which is what the served VAST declares as `<Duration>`; a spot longer than the 30-second slot also comes back as a non-fatal entry in the response `warnings` array. The probe never blocks the create — on failure the duration stays null and the VAST falls back to a flat 30s. */\n video_url?: string;\n};\n\nexport type PartnerSelfPromoPlacement = {\n /** One entry per `partner-self-promo` placement on that screen, each carrying `allocation_id`, `status`, `start_date`, `end_date` and a TRIMMED `advertisement` (`_id`, `name`, `video`, `photos`, `status` only — no `description`, `campaign_link` or `createdAt`). The underlying read filters out `deactivated`/`deleted`/`rejected` advertisements, so a disabled promo disappears from here even though its placement row still exists. */\n ads?: Array<{\n advertisement?: PartnerSelfPromoAd;\n allocation_id?: string;\n end_date?: string;\n start_date?: string;\n status?: string;\n }>;\n /** Trimmed to `_id` and `name` only. EVERY non-deactivated screen the partner owns appears in this listing, including ones with an empty `ads` array — the response is a full screen roster, not just the placed subset. */\n screen?: {\n _id?: string;\n name?: string;\n };\n};\n\nexport type PartnerStats = {\n /** Recomputed by the daily stats cron as the count of the partner's screens in status `approved`/`active` — an APPROVAL count, not a liveness count. The identically-named field on `GET /v1/partner/analytics` is instead derived live from `online_status`, so the two measure different things. */\n active_devices?: number;\n /** ISO timestamp stamped by the partner impression-record endpoints on each recorded impression. Only those endpoints write it, so it does not move for revenue booked through other rails. */\n last_impression_at?: string;\n /** Integer US cents already reserved by an in-flight payout request. Sourced from the typed `partners.pending_payout_cents` column (the JSONB key was dropped) and subtracted from the available balance so repeat payout requests cannot double-spend. */\n pending_payout_cents?: number;\n /** Read from the `partners.stats` JSONB cache, maintained by signed-delta counters on device register/delete. `GET /v1/partner/analytics` publishes the same field name from a LIVE device count instead, so the two can disagree. */\n total_devices?: number;\n /** Incremented per impression by the partner impression-record path, then OVERWRITTEN by the daily reconcile with `SUM(impressions_count)` over `programmatic_earnings_daily` — two different denominators writing one key, so its meaning depends on when in the cron cycle you read it. */\n total_impressions?: number;\n /** Integer US cents. Recomputed daily as `SUM(earner_revenue) * 100` across ALL of the partner's non-demo `programmatic_earnings_daily` rows, with no payable-source filter and no Net-60 maturity gate — so it can exceed `EarningsResponse.total_lifetime_cents`, which applies the payable filter. */\n total_revenue_cents?: number;\n};\n\n/**\n * One taxi-meter state change reported by the partner SDK's setTripState. (partner, device, trip_id, event_seq) is the event's identity.\n */\nexport type PartnerTripState = {\n /** The host's destination text or zone. */\n destination?: string;\n /** The event's position in the trip. Increases within a trip; a retry re-sends the same value (idempotency key with trip_id, per device). */\n event_seq: number;\n /** Fare so far, in the smallest currency unit. */\n fare_cents?: number;\n /** true while the meter runs (a hired trip). */\n meter_on: boolean;\n /** Riders the host counted. */\n passenger_count?: number;\n /** The host's payment type (stored trimmed and lowercased). */\n payment_type?: string;\n /** When the meter changed state (device clock), within the last 7 days and at most 5 minutes ahead of the server clock. */\n state_at: string;\n /** Tip, in the smallest currency unit. */\n tip_cents?: number;\n /** The host app's trip id. Unique per device, not per partner. */\n trip_id: string;\n};\n\n/**\n * Canonical INT-PK identity for partner variants (e.g. adipolo_lower_floor_tablet). Wave 2 of pristine-state refactor. variant_name TEXT remains as the wire-format alternate-key indefinitely.\n */\nexport type PartnerVariants = {\n archived: boolean;\n created_at: string;\n /** NUMERIC(10,2). */\n daily_budget_cap?: number | null;\n daily_impression_cap?: number | null;\n daily_request_cap?: number | null;\n /** Backfill is 1:N from demand_partner_config.variant_names[]. Rows with NULL demand_partner_id at apply time are silently skipped — deploy-controller previous-tick guard ensures Wave 2 A1 has populat... */\n demand_partner_id: number;\n /** vast_tag_variants JSONB on demand_partner_config still co-exists during transition. Backfill UPDATE pulls display_name from the JSONB if present (no-op today; forward-compatible). */\n display_name?: string | null;\n /** public_alias is the obfuscation alias for partner-API responses (matches partnerAlias.encodeAlias pattern: 'var_' || sha256(variant_name)[0..5]). */\n public_alias: string;\n request_pressure_base_interval_seconds?: number | null;\n /** NUMERIC(12,8). */\n request_pressure_completion_rate_floor?: number | null;\n /** NUMERIC(6,3). */\n request_pressure_confidence_level?: number | null;\n request_pressure_defer_seconds?: number | null;\n /** request_pressure_enabled=false fails open for pressure shaping only. It must not be treated as demand-source enablement. */\n request_pressure_enabled?: boolean | null;\n request_pressure_max_multiplier?: number | null;\n request_pressure_min_pair_requests?: number | null;\n request_pressure_min_source_requests?: number | null;\n request_pressure_probe_interval_seconds?: number | null;\n request_pressure_probe_modulo?: number | null;\n /** NUMERIC(10,4). */\n request_pressure_probe_target_per_minute?: number | null;\n updated_at: string;\n /** feeds_into lists tables that will gain a nullable variant_id INT FK in Wave 2 Stream B. Each is a separate PR — this PR mints the dimension only. */\n variant_id?: string | null;\n /** variant_name TEXT is the wire-format alternate-key (kept forever) — internal logs and partner integrations rely on it. Never propose dropping variant_name. */\n variant_name: string;\n};\n\n/**\n * Per-zone per-day CPM rates from partner APIs. Used for revenue estimation and settled partner-truth allocation when partner_reconciliation_reports does not provide a newer manual/API row. PG only.\n */\nexport type PartnerZoneCpmDaily = {\n created_at: string;\n /** Wave 2 B added demand_partner_id INT + variant_id INT FKs. Legacy partner_key TEXT kept as wire-format alternate-key. Backfill via follow-up Wave 2 B-backfill PR. */\n demand_partner_id?: number | null;\n /** NUMERIC(10,4). */\n ecpm: number;\n fetched_at: string;\n /** NUMERIC(6,4). */\n fill_rate?: number | null;\n /** NUMERIC(14,6). */\n gross_revenue?: number | null;\n id?: string | null;\n impressions?: number | null;\n partner_key: string;\n period_date: string;\n requests?: number | null;\n /** Daily earnings aggregation may use these rows to settle gross revenue, but completed_impressions remains the event-count source for programmatic_earnings_daily.impressions_count. */\n source: string;\n updated_at: string;\n variant_id?: number | null;\n zone_id: string;\n zone_label?: string | null;\n};\n\nexport type PayoutResponse = {\n /** Integer US cents actually reserved: `min(requested, available)`, where available is CONFIRMED earnings minus anything already pending. It must clear the partner's `payout_threshold_cents` (default 2500, i.e. $25.00) or the request is rejected `400`. */\n amount_cents?: number;\n /** Today plus 5 CALENDAR days, computed in the handler with no reference to banking days, the Stripe payout schedule, or whether any transfer exists. It is a display placeholder. */\n estimated_arrival?: string;\n /** Generated in the handler as `po_<epoch_ms>_<random>` and **not persisted anywhere** — no row, no Stripe object. It cannot be used to look the payout up afterwards. */\n payout_id?: string;\n /** Always the literal `processing` on a successful request — the other enum values are never returned by this endpoint. The only side effect is a bump to the partner's `pending_payout_cents` counter; no Stripe transfer is created here. */\n status?: \"pending\" | \"processing\" | \"completed\" | \"failed\";\n /** **Always `null`.** The handler returns a hardcoded null because it creates no transfer; a real transfer id appears only later on the corresponding `payout` row from `GET /earnings/transactions`. */\n stripe_transfer_id?: string | null;\n};\n\nexport type PlaygroundResult = {\n /** Whether the operation is safe for autonomous agents. */\n agent_safe: boolean;\n /** Billing tier: free, metered, or paid. */\n cost_tier: string;\n /** Whether the operation is idempotent. */\n idempotent: boolean;\n /** Object type identifier. */\n object: \"playground_result\";\n operation_id: string;\n /** Declared side effects of the operation. */\n side_effects: Array<string>;\n /** Whether the request body passed validation. */\n valid: boolean;\n /** The parsed/coerced body on success, null on failure. */\n validated_body?: unknown;\n /** Array of validation issues on failure, null on success. */\n validation_errors: Array<PlaygroundValidationError> | null;\n /** The HTTP method and path this operation maps to. */\n would_call: {\n method: string;\n path: string;\n };\n};\n\nexport type PlaygroundValidationError = {\n /** Zod issue code. */\n code: string;\n /** Human-readable validation message. */\n message: string;\n /** Dot-delimited path to the invalid field. */\n path: string;\n};\n\nexport type ProgrammaticSettingsInput = {\n /** Enable programmatic ads */\n enabled?: boolean;\n /** Minimum CPM in dollars */\n floor_price_cpm?: number;\n /** Accepted by this schema but silently discarded. The write path keeps only an explicit whitelist — `enabled`, `provider`, `vast_tag_url`, `timeout_ms`, `programmatic_allocation_percent`, `floor_price_cpm`, `currency`, `dynamic_floor_pricing`, `blocked_advertisers`, `blocked_iab_categories`, `preferred_deal_ids` — and `google_ima` is not in it. A body containing only this key persists nothing and still returns 200. */\n google_ima?: {\n ad_tag_url?: string;\n enabled?: boolean;\n max_duration?: number;\n };\n /** Accepted by this schema but silently discarded — not in the write whitelist, never persisted, and never read back (`header_bidding_settings.prebid` is JSON `null` on every row). A body containing only this key persists nothing and still returns 200. */\n prebid?: {\n bidders?: Array<{\n name?: string;\n params?: Record<string, unknown>;\n }>;\n enabled?: boolean;\n };\n /** Ad source priority */\n priority?: \"direct_first\" | \"programmatic_first\" | \"highest_bid\";\n};\n\nexport type ProgrammaticSettingsResponse = {\n /** The master serving gate `earner_screens.programmatic_enabled`, which is `NOT NULL DEFAULT true` — so it reads `true` for a screen nobody has ever configured — and NOT the legacy `header_bidding_settings.enabled` shadow. Setting it false stops programmatic serving for the screen; the write invalidates the serving caches, and a missed invalidation still drains within the 120s hot-cache TTL. */\n enabled?: boolean;\n /** Minimum clearing price in DOLLARS per thousand impressions (not cents), default `0.50`, accepted range 0.01-100, denominated in the screen's `currency`. It is a floor only: the auction clears at the `max()` of this, any floor rule, the dynamic floor and any matched deal floor, so a low value here does not guarantee cheap fill and can never lower a rule-derived floor. */\n floor_price_cpm?: number;\n /** Never emitted. The response formatter builds the entire body and has no `google_ima` key, and the string appears nowhere on this API's request or response surface — there is no producer. Configure the IMA tag through the screen's `vast_tag_url` / VAST waterfall instead. */\n google_ima?: {\n ad_tag_url?: string;\n enabled?: boolean;\n max_duration?: number;\n };\n /** Constant discriminator `programmatic_settings` (Stripe-style envelope). It is present on the GET, the PUT and the PATCH alike, so it identifies the resource type and tells you nothing about which verb produced the response. */\n object?: string;\n /** Never emitted. The response formatter has no `prebid` key, and the underlying `header_bidding_settings.prebid` is JSON `null` on every stored row with no typed home — nothing populates it on either side. */\n prebid?: {\n bidders?: Array<Record<string, unknown>>;\n enabled?: boolean;\n };\n /** Never emitted, and not settable. The response formatter has no `priority` key and the field is absent from the write whitelist, so `direct_first` / `programmatic_first` / `highest_bid` can neither be stored through this endpoint nor read back from it. */\n priority?: string;\n /** Server-minted per HTTP request in the form `req_<32 hex>`, and also returned in the `X-Request-Id` response header. A client-supplied `X-Request-Id` is echoed back separately as `X-Client-Request-Id` and never becomes this value. Unrelated to `HeaderBiddingSettings.request_id`, which identifies a VAST waterfall attempt. */\n request_id?: string;\n /** Echoed straight back from the path parameter — it is not re-read from the stored row. Must be a 24-character hex Mongo ObjectId; anything else is rejected before any lookup happens. */\n screen_id?: string;\n /** Never emitted — the response formatter has no `stats` key, so `impressions_today`, `fill_rate_percent` and `avg_cpm` have no producer here; use the partner analytics endpoints for delivery numbers. When you do, note that partner-rail impression counts are delivery accounting, not billing: only `completed_impressions` settles revenue. */\n stats?: {\n avg_cpm?: number;\n fill_rate_percent?: number;\n impressions_today?: number;\n };\n /** The SCREEN row's `updated_at`, so any change to the screen moves it — not just a programmatic-settings change. When the row carries no timestamp the server substitutes the current time, so this can never be relied on as 'when these settings last changed' or used as a change-detection cursor. */\n updated_at?: string;\n};\n\nexport type RequestMetadataResponse = {\n duration_ms: number;\n /** Present only when the original request resulted in an error. */\n error?: {\n code: string;\n type: string;\n };\n method: string;\n /** Object type identifier. */\n object: \"request\";\n path: string;\n request_id: string;\n status_code: number;\n timestamp: string;\n};\n\n/**\n * The EFFECTIVE content policy for a screen + its provenance. Whole-layer\n * coalescing: the effective policy comes from exactly ONE layer.\n *\n */\nexport type ResolvedContentPolicy = {\n /** All content_preferences keys + blocked_iab_categories. */\n effective?: Record<string, unknown>;\n /** Always `resolved_content_policy`. This resource is read-only introspection — a \"why is this screen serving what it serves\" preview — and unlike the content-preferences response it IS wrapped in `{ success, data }`. */\n object?: string;\n /** Which single layer supplied the whole `effective` object. `source` is `screen` when the screen's OWN policy is explicit — specifically `content_filtering_enabled === true`, or any of the six content lists non-empty, or a non-empty `programmatic_settings.blocked_iab_categories`; the `allow_*` toggles and the two scalars NEVER make it explicit because they always carry a default. Otherwise it is `group` (lowest `group_id` wins, and `group_id` is present only then), then `network` (the partner's `network_config.default_content_preferences`, used only when it has at least one populated key and merged onto the system baseline), then `default`. */\n provenance?: {\n /** Present only when source=group. */\n group_id?: string;\n source?: \"screen\" | \"group\" | \"network\" | \"default\";\n };\n /** The screen whose effective policy this is, echoed from the path parameter as a string. Ownership is checked first, so a screen you do not own returns 404 rather than an empty policy. */\n screen_id?: string;\n};\n\n/**\n * The EFFECTIVE sensing view for a screen + its provenance (screen\n * deployment ?? group sensing config ?? default).\n *\n */\nexport type ResolvedSensingConfig = {\n /** The profile scalars actually in force after resolution. When the winning layer is a group, the base profile is hydrated first and only the group's NON-null overrides are laid over it, so this is always a complete view rather than a sparse patch. */\n effective?: {\n capture_interval_ms?: number | null;\n estimated_fps_impact?: number | null;\n min_confidence?: number | null;\n profile_name?: string | null;\n report_interval_ms?: number | null;\n };\n /** Stripe-style type discriminator; always the literal `resolved_sensing_config`, so it is safe to switch on. */\n object?: string;\n /** Which layer won: `screen` (the screen's own active deployment), `group_default` (a group sensing config, with `group_id` naming it), or `default` (nothing bound). Resolution is whole-layer — `effective` never mixes values from two sources. */\n provenance?: {\n /** Present only when source=group_default. */\n group_id?: string;\n source?: \"screen\" | \"group_default\" | \"default\";\n };\n /** The screen's Mongo id — the same identifier the route surfaces call `screenMongoId`. Echoes the path parameter. */\n screen_id?: string;\n};\n\n/**\n * Permission flags for each role. Owners have all permissions,\n * admins have operational permissions, viewers have read-only access.\n *\n */\nexport type RolePermissions = {\n /** Can configure webhooks */\n configure_webhooks?: boolean;\n /** Can edit screen settings */\n edit_screens?: boolean;\n /** Can invite/remove team members */\n manage_team?: boolean;\n /** Can request payouts */\n request_payouts?: boolean;\n /** Can rotate API keys */\n rotate_api_keys?: boolean;\n /** Can view impression analytics */\n view_analytics?: boolean;\n /** Can view earnings/revenue */\n view_earnings?: boolean;\n /** Can view impression data */\n view_impressions?: boolean;\n /** Can view payout history */\n view_payouts?: boolean;\n /** Can view online/offline status */\n view_screen_status?: boolean;\n /** Can view screen list */\n view_screens?: boolean;\n};\n\nexport type SDKConfig = {\n /** Seconds between ads */\n ad_interval?: number;\n /** The only `sdk_config` key that changes what is SERVED: if it contains `image`, the server stops substituting a transcoded MP4 for an image creative on `lite_sdk`/`full_sdk` tiers, because you are declaring the client can render images itself. Frozen at the quick-start default `[\"video\", \"image\"]` — there is no partner-facing endpoint that writes `sdk_config`. */\n allowed_ad_types?: Array<string>;\n /** Maximum number of ads the `trillboards-connect` client retains for offline playback (`cacheAdsOffline`). Client-side hint only — the server never reads it — and it is frozen at the quick-start default of 10 because no partner-facing endpoint writes `sdk_config`. */\n cache_size?: number;\n /** Seconds to display image ads */\n image_duration?: number;\n /** Fleet-wide hourly ad cap echoed as `settings.max_ads_per_hour` in the `/ads` response, but only as the FALLBACK behind `network_config.ad_delivery.max_ads_per_hour` — that is the writable control, and this one is frozen at the quick-start default of 20 because no partner-facing endpoint writes `sdk_config`. */\n max_ads_per_hour?: number;\n /** Label the lite SDK paints on the tap-to-continue overlay; when absent the SDK falls back to the literal `TAP TO CONTINUE`, which is also the quick-start default stored for every partner. Frozen — there is no partner-facing endpoint that writes `sdk_config`. */\n overlay_button_text?: string;\n /** Has no reader anywhere in the server or the shipped SDKs — the `settings.sound_enabled` a player actually obeys is resolved per screen from `earner_screens.sound_enabled` / display preferences, not from this partner-level value. Frozen at the quick-start default `false`. */\n sound_enabled?: boolean;\n};\n\n/**\n * A partner screen — the client-facing projection of a registered\n * device+screen pair (`@trillboards/api-client` `Screen`). The screens\n * resource reads the partner device endpoints.\n *\n */\nexport type Screen = {\n /** City resolved at registration by reverse/forward geocoding or taken from your own `location.city`, with placeholder strings (\"City\", \"Vending Machine Location\") coalesced to null rather than stored. The screens resource reads the partner device endpoints, whose payload carries this as `location.city`. */\n city?: string;\n /** Country of the screen, taken from the enriched screen row with a fallback to the device row. The device row DEFAULTS this to `\"US\"` when registration could not resolve a country, so it is effectively never null and a `US` value can mean \"unresolved\". The screens resource reads the partner device endpoints, whose payload carries this as `location.country`. */\n country?: string;\n /** Creation time of the DEVICE row backing the screen, not of the screen record itself — the screens resource is a projection of the partner device endpoints, and the screen can be created later than its device. */\n created_at: string;\n /** The paired device identifier. */\n device_id?: string;\n /** Player embed URL for the screen. */\n embed_url?: string;\n /** Friendly screen/venue name. */\n name: string;\n /** Display resolution label (e.g. \"1920x1080\") when known. */\n resolution?: string;\n /** Stable screen identifier. */\n screen_id: string;\n /** Screen lifecycle status (e.g. pending, active, offline, suspended). */\n status: string;\n /** IANA zone, and it is ONLY ever what you supplied as `location.timezone` at registration — nothing derives it from the resolved coordinates, so a large share of screens carry null. The screens resource reads the partner device endpoints, whose payload carries this as `location.timezone`, and it is the one geo field read device-first rather than screen-first. */\n timezone?: string;\n /** `earner_screen_devices.updated_at` of the backing device, which the device-mirror write buffer stamps `NOW()` on every flush including heartbeats. It moves continuously on a live screen, so it cannot be used to detect a configuration change. */\n updated_at: string;\n /** Granular OpenOOH venue type of the screen's location. */\n venue_type?: string;\n};\n\nexport type ScreenAnalyticsResponse = {\n /** `total` is deduped, GIVT-passed `completed_impressions` over the period. `daily_average` divides by the number of days that RETURNED A ROW, not by the period length, so zero-impression days do not dilute it. `trend_percent` compares the second half of the returned rows against the first half and is `0` whenever the earlier half is zero. */\n impressions?: {\n daily_average?: number;\n total?: number;\n /** Percentage change compared to previous period */\n trend_percent?: number;\n };\n /** `days` is 7, 30 or 90; any other `?period=` value silently becomes 7. `start` is midnight (API-server local time) that many days back and `end` is the moment the request was served, so the window is not a whole number of days. */\n period?: {\n days?: number;\n end?: string;\n start?: string;\n };\n /** `location` is a `City, State` string or `null` when either part is missing. `online_status` comes from the live heartbeat mirror and is `online`/`offline`/`unknown`, where `unknown` means the screen has never heartbeated — it is not a synonym for offline. `status` is the separate approval state. */\n screen?: {\n id?: string;\n location?: string;\n name?: string;\n online_status?: string;\n status?: string;\n };\n /** One entry per day that had impressions — days with none are absent, not zero-filled, which is why `daily_average` and this array can disagree with the period length. **`avg_duration_ms` is always `0`**: it is a hardcoded literal in both the ClickHouse query and the Postgres fallback. */\n timeline?: Array<{\n avg_duration_ms?: number;\n date?: string;\n impressions?: number;\n }>;\n};\n\n/**\n * A reusable, named, partner-scoped set of screens that analytics,\n * moderation policy, sensing/model deployment, fleet commands, and\n * sensing routes can target. Members are always a subset of the\n * partner's own screens.\n *\n */\nexport type ScreenGroup = {\n created_at?: string;\n /** Free-text note. Returned as an explicit `null` rather than omitted when unset, and an empty string supplied at create is stored as `null`. A PATCH writes only the keys you actually send, so omitting it preserves the current value. */\n description?: string | null;\n /** Screen group id: the literal prefix `sg_` followed by a v4 UUID (`pgRepo/screenGroups.js` mints `sg_${crypto.randomUUID()}`). Treat it as an opaque string; do not parse the UUID half. */\n id?: string;\n /** Required, non-empty label. It is NOT unique and has no length constraint in the database, so two of your groups may share a name; `id` (`sg_<uuid>`) is the only stable handle. */\n name?: string;\n /** Constant string `screen_group` — the Stripe-style type discriminator. It is present on every group payload, including inside a webhook's `data`, so a single handler can switch on it. */\n object?: string;\n /** Distinct member screens (COUNT and member list share one junction — they cannot diverge). */\n screen_count?: number;\n /** Bumped by membership changes as well as metadata edits — `addMembers`, `removeMembers` and the soft delete all set `updated_at = NOW()` on the group row — so it is a change marker for the group INCLUDING its member set, not just its name and description. */\n updated_at?: string;\n};\n\n/**\n * The typed content/moderation policy a screen group carries (Phase 4 live\n * policy). `null` when the group has no policy of its own. Member screens\n * inherit it at read/serve time when they have no explicit own policy\n * (screen ?? group ?? partner network default ?? system default). Mirrors\n * the per-screen content_preferences keys 1:1; blocked_iab_categories is\n * first-class here (the auction bcat).\n *\n */\nexport type ScreenGroupContentPolicy = {\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_adult_content`. A `false` also adds `IAB25` plus `IAB25-2`..`IAB25-5` to the `bcat` and `sexually_suggestive`/`sexual_health`/`sensationalism` to the GAM `excl_cat`; unset falls back to the platform default of `false`. */\n allow_adult_content?: boolean;\n /** Rejects a direct ad only when set to `false` AND the ad's classification carries `contains_alcohol`. A `false` also adds `IAB8-5`/`IAB8-18` to the outgoing OpenRTB `bcat` and `alcohol` to the GAM `excl_cat`; when the key is unset that list-building step uses the platform default, which for alcohol is `true` (not blocked). */\n allow_alcohol?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_gambling`. A `false` also adds `IAB9-7` to the `bcat` and `gambling`/`online_gambling` to the GAM `excl_cat`; unset falls back to the platform default of `false`. */\n allow_gambling?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_political`. A `false` also adds `IAB11-4` to the `bcat` and `politics` to the GAM `excl_cat`; unset falls back to the platform default of `true` (allowed). */\n allow_political?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_profanity`. A `false` also adds `IAB25-2` to the `bcat` and `sensationalism` to the GAM `excl_cat`; unset falls back to the platform default of `false`. */\n allow_profanity?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_religious`. A `false` also adds `IAB23` to the `bcat` and `religion` to the GAM `excl_cat`; unset falls back to the platform default of `true` (allowed). */\n allow_religious?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_tobacco`. A `false` also adds `IAB25-6` to the `bcat` and `drugs_tobacco` to the GAM `excl_cat`; unset falls back to the platform default of `false`, so tobacco is blocked on the programmatic side even when you never configure it. */\n allow_tobacco?: boolean;\n /** Rejects a direct ad only when `false` AND the ad's classification carries `contains_violence`. A `false` also adds `IAB25-3`/`IAB25-4` to the `bcat` and `violence`/`military_conflict` to the GAM `excl_cat`; unset falls back to the platform default of `false`. */\n allow_violence?: boolean;\n /** Consulted only when non-empty, and matched against the ad's typed `iab_categories` with `categoriesMatch`, so IAB codes and human-readable names both work (categories are inferred from subjects/keywords when the ad carries none). Under `lenient` and `permissive` a non-match is allowed through anyway, which makes the list inert at those levels; it also adds +10 per match to the content match score. */\n allowed_categories?: Array<string>;\n /** A SOFT preference, not a filter: it never rejects anything. Matching keywords add +3 each to the content match score used for prioritisation, and the approval path explicitly skips this list. */\n allowed_keywords?: Array<string>;\n /** Matched by case-insensitive SUBSTRING containment against the ad's `moderation.analysis.subjects`, not equality. Consulted only when non-empty and inert under `lenient`/`permissive`, like `allowed_categories`; it also adds +5 per match to the content match score. */\n allowed_subjects?: Array<string>;\n /** When `true` and the ad's classification says `is_known_brand`, approval returns IMMEDIATELY — the `blocked_categories`, `blocked_subjects`, `blocked_keywords` and every `allowed_*` check below it never run. The `allow_*` toggles and the age-rating check sit above it and still apply. */\n auto_approve_known_brands?: boolean;\n /** Same matching as `allowed_categories` (IAB code or human-readable name, via `categoriesMatch`), but a single hit rejects the ad at every `preference_level`. The rejection reason reports the human-readable name rather than the code you stored. */\n blocked_categories?: Array<string>;\n /** The auction `bcat` sent on OpenRTB bid requests for member screens. It is NOT part of `content_preferences`: on an individual screen the same list lives on `programmatic_settings.blocked_iab_categories`, and a non-empty one there is one of the three conditions that makes the screen layer explicit and bypasses this group policy entirely. */\n blocked_iab_categories?: Array<string>;\n /** The keyword list that DOES block, unlike `allowed_keywords`. Case-insensitive substring containment against the ad's `moderation.analysis.keywords`, so a short entry matches inside longer words. */\n blocked_keywords?: Array<string>;\n /** Case-insensitive substring containment against the ad's `moderation.analysis.subjects` — `bar` therefore also blocks `barbecue`, so use whole distinctive words. A hit is a hard reject. */\n blocked_subjects?: Array<string>;\n /** When `false`, direct-ad moderation short-circuits and every ad is auto-approved for member screens. It does NOT disable brand safety on the programmatic side — the `allow_*` toggles are still translated into the OpenRTB `bcat` and GAM `excl_cat` regardless of this flag. */\n content_filtering_enabled?: boolean;\n /** The group this row belongs to. It is also the tiebreak: a screen that is a member of more than one policy-bearing group inherits from the LOWEST `group_id`, so a screen should be in at most one. */\n group_id?: string;\n /** Highest ad classification a member screen accepts. `G` is deliberately NOT the strictest setting — the matcher silently rewrites it to `PG-13` for backwards compatibility — and `preference_level` then adds its buffer on top, so `lenient` with `PG-13` actually admits `R`. */\n max_age_rating?: \"G\" | \"PG\" | \"PG-13\" | \"R\" | \"NC-17\";\n /** Always the literal `screen_group_content_policy`. The policy is returned as `data.content_policy`, and a group carrying no policy of its own returns a bare `null` there — so on that path there is no `object` to branch on at all. */\n object?: string;\n /** Selects a strictness profile, not a label. It sets the age-rating buffer (`strict`/`moderate` 0, `lenient` +1, `permissive` +2 notches above `max_age_rating`), whether an ad with no AI classification is auto-approved (`lenient`/`permissive` yes; `strict`/`moderate` reject and queue re-moderation), and how the `allowed_*` lists behave on a non-match — `strict` rejects, `moderate` lets common business categories through, `lenient`/`permissive` make them inert. */\n preference_level?: \"strict\" | \"moderate\" | \"lenient\" | \"permissive\";\n /** ISO-8601 timestamp of the last write to this policy row, or `null` when the row has none. Member screens read the resolved policy through a screen-keyed Redis cache that a policy, membership or group-delete write busts, so an edit here reaches serving immediately rather than after the 60-second TTL. */\n updated_at?: string;\n} | null;\n\n/**\n * Partial, non-clobbering — only the fields present are written. Same keys\n * the per-screen content-preferences PUT accepts, plus blocked_iab_categories.\n *\n */\nexport type ScreenGroupContentPolicyInput = {\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB25` and `IAB25-2`..`IAB25-5` to the member screens' `bcat`; unset already blocks it via the platform default. */\n allow_adult_content?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` both rejects classified alcohol creatives on member screens and adds `IAB8-5`/`IAB8-18` to their outgoing `bcat`. */\n allow_alcohol?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB9-7` to the member screens' `bcat`; unset already blocks gambling programmatically via the platform default. */\n allow_gambling?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB11-4` to the member screens' `bcat`; the platform default here is `true`, so political ads run unless you set this. */\n allow_political?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB25-2` to the member screens' `bcat`; unset already blocks it via the platform default. */\n allow_profanity?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB23` to the member screens' `bcat`; the platform default here is `true`, so religious ads run unless you set this. */\n allow_religious?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB25-6` to the member screens' `bcat`; leaving it unset already blocks tobacco programmatically via the platform brand-safety default. */\n allow_tobacco?: boolean;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Writing `false` adds `IAB25-3`/`IAB25-4` to the member screens' `bcat`; unset already blocks it via the platform default. */\n allow_violence?: boolean;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and the allow/block pair is conflict-checked case-insensitively against this patch MERGED onto the stored row, so it can 400 with `conflicting_lists` on a term you set in an earlier request. */\n allowed_categories?: Array<string>;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and it is conflict-checked against `blocked_keywords`, even though at serve time this list never rejects anything — it only contributes to the content match score. */\n allowed_keywords?: Array<string>;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and the Subjects allow/block pair is conflict-checked against the stored row merged with this patch. Note these are matched by substring at serve time, not equality. */\n allowed_subjects?: Array<string>;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Setting it `true` makes a known-brand ad bypass every category, subject and keyword check on member screens, so treat it as a widening, not a convenience. */\n auto_approve_known_brands?: boolean;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and blocking a category an earlier PUT allowed fails with `conflicting_lists` even though this request names only one side of the pair. */\n blocked_categories?: Array<string>;\n /** Array of strings capped at 100 by a DIFFERENT constant from the other lists (`PROGRAMMATIC_SETTINGS_LIMITS.max_blocked_iab_categories`), reported as `blocked_iab_categories_too_large`. It is not conflict-checked, because there is no `allowed_iab_categories` counterpart. */\n blocked_iab_categories?: Array<string>;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and unlike `allowed_keywords` this one really does reject, by case-insensitive substring match against the ad's analysed keywords. */\n blocked_keywords?: Array<string>;\n /** Array of strings, at most 100 items (`array_too_large`); a non-array is `field_must_be_array` and a non-string element is `array_items_must_be_strings`. Writing it REPLACES the stored list — there is no append — and it is a hard reject at serve time, matched by case-insensitive substring — so a short entry blocks more than you intend. */\n blocked_subjects?: Array<string>;\n /** Must be a real JSON boolean — the strings `\"true\"`/`\"false\"` are rejected with `field_must_be_boolean`, unlike the L-bar PUT which accepts them. Omitting the key leaves the stored value untouched; there is no way to reset a key back to \"inherit\". */\n content_filtering_enabled?: boolean;\n /** One of `G`, `PG`, `PG-13`, `R`, `NC-17`; anything else is `invalid_age_rating`. Writing `G` does not give you the strictest policy — the matcher later treats `G` as `PG-13`. */\n max_age_rating?: \"G\" | \"PG\" | \"PG-13\" | \"R\" | \"NC-17\";\n /** One of `strict`, `moderate`, `lenient`, `permissive`; anything else is rejected with `invalid_preference_level`. Validated against the same constant the per-screen content-preferences PUT uses, so the two surfaces cannot drift. */\n preference_level?: \"strict\" | \"moderate\" | \"lenient\" | \"permissive\";\n};\n\n/**\n * Webhook body for a Screen Group lifecycle event (`screen_group.created` | `updated` |\n * `deleted` | `member.added` | `member.removed` | `content_policy.updated` |\n * `sensing_config.updated`). Standard `{ id, event, timestamp, data }` envelope: `id` (`evt_…`)\n * is the idempotency key, and `data` is the Stripe-style `screen_group` /\n * `screen_group_content_policy` / `screen_group_sensing_config` object the REST API returns.\n * The event type is also in the `X-Trillboards-Event` header — verify the compound\n * `X-Trillboards-Signature` over the raw body first.\n *\n */\nexport type ScreenGroupEvent = {\n /** The screen_group / content-policy / sensing-config object. */\n data?: Record<string, unknown>;\n /** The event type, also sent in the `X-Trillboards-Event` header and mirrored to the SSE stream at `GET /v1/partner/events/stream`. This whole event family is gated by the `SCREEN_GROUP_EVENTS_ENABLED` runtime flag, which is OFF by default — when off, nothing is enqueued at all. */\n event?: \"screen_group.created\" | \"screen_group.updated\" | \"screen_group.deleted\" | \"screen_group.member.added\" | \"screen_group.member.removed\" | \"screen_group.content_policy.updated\" | \"screen_group.sensing_config.updated\";\n /** `evt_<base36 epoch ms>_<12 hex>`, minted once per occurrence. Use it as the idempotency key: a retried delivery of the same occurrence reuses it, but the same id is also sent to EVERY endpoint you have subscribed to that event, so dedupe per (endpoint, id). It is not the server-side delivery audit id and cannot be looked up. */\n id?: string;\n /** Stamped when the server ENQUEUED the delivery, not when it was sent, so a retried delivery still carries the original value and can trail wall-clock by the retry backoff. It is not the resource's own `updated_at`. */\n timestamp?: string;\n};\n\n/**\n * A screen group's sensing profile binding (profile_name) + typed\n * performance overrides. `null` when the group has none. Member screens\n * inherit via the sensing resolver's group step when they have no\n * screen-specific deployment.\n *\n */\nexport type ScreenGroupSensingConfig = {\n /** Edge frame-capture cadence overrides pushed to member screens (SensingConfig.capture keys, milliseconds). Group wins over the profile default per-key when set. */\n capture?: {\n faceTriggeredIntervalMs?: number | null;\n minCaptureIntervalMs?: number | null;\n periodicIntervalMs?: number | null;\n vehicleTriggeredIntervalMs?: number | null;\n } | null;\n /** Group override of the profile's declared frame-capture cadence in milliseconds; `null` inherits the base profile's value. Treat it as advisory - this legacy scalar does not reach the device capture loop (the edge `FrameCaptureManager` reads the nested `capture` block), and observed cadence was measured at ~12x the declared value against a fleet floor of one analysis per 30 s. */\n capture_interval_ms?: number | null;\n /** When `false` the whole group step is a no-op and member screens fall straight through to the next layer of the resolver — the row is retained but contributes nothing, which is how you disable group sensing without losing the overrides. */\n enabled?: boolean;\n /** Advisory workload cost of the profile, normally derived server-side as the sum of the selected models' registry `fps_cost` (built-ins run 5.5-12.0). It is display metadata only — no device or pipeline code reads it, so overriding it throttles nothing. */\n estimated_fps_impact?: number | null;\n /** The `sg_`-prefixed Screen Group this config belongs to; echoes the `groupId` path parameter. */\n group_id?: string;\n /** Detection-confidence floor in 0..1, overlaid onto the resolved profile's `thresholds.min_confidence`; built-in profiles ship 0.35-0.45. `null` inherits the base profile's value. */\n min_confidence?: number | null;\n /** Stripe-style type discriminator; always the literal `screen_group_sensing_config`, so it is safe to switch on. */\n object?: string;\n /** The sensing profile members inherit, bound by NAME (a builtin or an existing custom profile) rather than by `profile_id`, because `setDeployment` clones a fresh `profile_id` per screen on every redeploy. `null` means the group binds no profile. */\n profile_name?: string | null;\n /** Group override of how often the device batches observations back to the server, in milliseconds; `null` inherits the base profile's value. Built-in profiles ship 15000-30000. */\n report_interval_ms?: number | null;\n /** When this group's sensing config row was last written. The write also busts the resolver caches for the members as they stood at that instant and emits a `screen_group.sensing_config.updated` webhook, so a screen added to the group afterwards picks the config up on its own next cache miss instead. */\n updated_at?: string;\n /** Per-screen cloud-vision budget for member screens. screen_hourly_pace wins when both are set; otherwise derived from screen_daily_calls. null = no group budget (fleet keeps platform-default caps). */\n vision_budget?: {\n screen_daily_calls?: number | null;\n screen_hourly_pace?: number | null;\n } | null;\n} | null;\n\n/**\n * Partial, non-clobbering. A null override means \"inherit that field from\n * the base profile\". profile_name must resolve to a builtin or an existing\n * custom profile (else 400).\n *\n */\nexport type ScreenGroupSensingConfigInput = {\n /** Edge frame-capture cadence block (milliseconds); null clears it. Unknown keys are rejected (400). */\n capture?: {\n faceTriggeredIntervalMs?: number | null;\n minCaptureIntervalMs?: number | null;\n periodicIntervalMs?: number | null;\n vehicleTriggeredIntervalMs?: number | null;\n } | null;\n /** Frame-capture cadence override in milliseconds (100..3600000); `null` means inherit from the base profile. Advisory in practice: this legacy scalar does not reach the device capture loop (the edge reads the nested `capture` block), the fleet floors at one analysis per 30 s, and observed cadence was measured at ~12x the declared value. */\n capture_interval_ms?: number | null;\n /** Set `false` to make the group step a no-op — members fall through to the next resolver layer while the overrides stay stored, so this is the reversible way to switch group sensing off. */\n enabled?: boolean;\n /** Advisory workload-cost number (must be >= 0); `null` inherits the profile's own value, which the server derives from the selected models' registry `fps_cost`. Purely descriptive — setting it changes what readback and operator surfaces display and throttles nothing. */\n estimated_fps_impact?: number | null;\n /** Detection-confidence floor in 0..1 written into the resolved profile's `thresholds.min_confidence`; `null` inherits the base profile's value (built-ins are 0.35-0.45). */\n min_confidence?: number | null;\n /** Bind the group to a builtin or existing custom profile BY NAME; a name that resolves to neither is a 400. `null` clears the binding. Never a `profile_id` — that is re-minted per screen on each redeploy. */\n profile_name?: string | null;\n /** Observation-reporting cadence override in milliseconds (100..3600000); `null` means inherit from the base profile. */\n report_interval_ms?: number | null;\n /** Per-screen cloud-vision budget block; null clears it. Unknown keys are rejected (400). */\n vision_budget?: {\n screen_daily_calls?: number | null;\n screen_hourly_pace?: number | null;\n } | null;\n};\n\n/**\n * High-level sensing workload intent. Hardware tier and device telemetry are read-only server facts.\n */\nexport type SensingPerformanceMode = \"auto\" | \"presence_only\" | \"demographics\" | \"advanced\";\n\n/**\n * A Sense Anything Route — a (condition → channel actions) alert/automation rule.\n */\nexport type SensingRoute = {\n /** Every channel action fired when the route matches; 1 to 10 per route, each with its own `enabled` flag so one can be muted without deleting the route. `webhook` is the only channel with no external side effect — `whatsapp`, `sms`, `email` and `slack` deliver a real message to a real person, so point a test route at `webhook`. */\n actions?: Array<SensingRouteAction>;\n /** One bit per weekday (127 = always). */\n activeDaysMask?: number;\n /** Inclusive end of the daily active window, in minutes past local midnight in `activeTz` (0..1439). When it is LESS than `activeStartMinute` the window is read as overnight — 1200 to 360 means 20:00 through 06:00 — rather than as an empty range. */\n activeEndMinute?: number | null;\n /** Inclusive start of the daily active window, in minutes past local midnight in `activeTz` (0..1439; 1200 = 20:00). Ignored unless `activeTz` is set, and a `null` on either end makes the window whole-day. */\n activeStartMinute?: number | null;\n /** IANA zone name (e.g. `America/New_York`) the active window is evaluated in, trimmed to 64 characters. `null` disables the ENTIRE active-window check — `activeStartMinute`, `activeEndMinute` and `activeDaysMask` do nothing without it — and a zone the runtime cannot parse fails OPEN (always active) rather than silently dropping alerts. */\n activeTz?: string | null;\n /** `null` on a `count`-metric digest route (there is nothing to aggregate over). On an `avg`/`max` digest it carries only `field`, with `op` and `value` absent — only a `threshold`-mode route has a complete condition. */\n condition?: SensingRouteCondition | null;\n createdAt?: string;\n /** Minimum gap in seconds between alerts for the same (route, screen) episode; clamped to 0..86400 and defaulting to 300. It also caps the falling-edge clear window (60 s maximum), so a shorter debounce re-arms the route sooner as well as spacing alerts more tightly. */\n debounceSeconds?: number;\n /** Scheduled-aggregate configuration; `null` on `threshold` routes. `nextRunAt` is a server-held cursor the digest cron advances after every send, so it moves on its own without any edit from you. */\n digest?: {\n intervalMinutes?: number;\n metric?: \"count\" | \"avg\" | \"max\";\n nextRunAt?: string;\n windowMinutes?: number;\n } | null;\n /** Whether the evaluator considers this route at all. A disabled route keeps its condition, actions and screen set and is simply skipped by every hot-path read; deleting a route also sets this `false` (rows are soft-deleted, never removed). */\n enabled?: boolean;\n /** `threshold` evaluates `condition` against each incoming observation. `digest` ignores the condition and instead sends a scheduled aggregate driven by the `digest.nextRunAt` cursor; focus regions and explicit review timing are rejected on digest routes. */\n mode?: \"threshold\" | \"digest\";\n /** Operator-supplied label, trimmed and capped at 200 characters. It is delivered verbatim as `route_name` on every fire but is not the alert text — the human-readable message is composed separately from the screen's sensing intent. */\n name?: string;\n /** The owning principal's id, compared against your credential on every read and write — a mismatch answers 404 (existence-hidden), never 403. Equal to your partner id for routes created here. */\n ownerId?: string;\n /** Which principal created the route: `partner` for anything created through this API, `earner_user` for a route the screen's own operator created in the earner portal. The Partner API only ever lists or edits `partner` routes, but an `earner_user` route on your screen still delivers webhooks to you. */\n ownerKind?: string;\n /** The partner the route was created under. Equal to `ownerId` for partner-created routes and `null` for a route an earner created in the portal, so it is not a reliable join key for the screen's partner. */\n partnerId?: string;\n /** Binds the route to a sensing profile BY NAME, never by `profile_id` — redeploying a profile mints a fresh `profile_id` per screen, which would orphan the binding. `null` means unbound; when set, the route stays dormant on any screen whose active profile has a different name. */\n profileName?: string | null;\n reviewTimingMode?: SensingRouteReviewTimingMode;\n reviewWaitSeconds?: SensingRouteReviewWaitSeconds;\n /** Server-minted immutable id: the literal prefix `rt_` followed by a UUID. Webhook consumers should key on this rather than `route_name`, which the owner can rename at any time. */\n routeId?: string;\n /** Per-camera areas, at most one entry per screen. The legacy cloud path converts geometry into prompt text and is not geometrically enforced (measured 2026-08-25 on gemini-3.1-flash-lite). The retained local connector masks actual pixels separately for each declared scope; original unmasked JPEGs remain alert evidence. Retained installation and model accuracy require separate qualification. */\n screenFocusRegions?: Array<SensingRouteScreenFocusRegion>;\n /** The screens this route watches — deduped and capped at 250. For a group-targeted route these are the group's LIVE members resolved at read time, not a stored set, so the list can change between two GETs without any edit. */\n screenMongoIds?: Array<string>;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainMinSamples?: number;\n /** How long the condition must hold before the FIRST alert, in seconds (600 = ten minutes). Omit to alert on the first true reading. Cadence-independent, because the evaluator compares timestamps - correct however often the screen reports, including when that rate changes while the condition holds. */\n sustainSeconds?: number | null;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainWindowSeconds?: number;\n /** The sg_ Screen Group this route targets. When set, the route fires on the group's LIVE members (screenMongoIds reflects the resolved members at read time) and no explicit screens are stored. */\n targetGroupId?: string | null;\n /** Last write to the route row — NOT a configuration-change stamp. The digest cron bumps it every time it advances a digest route's next-run cursor, and a soft delete bumps it too, so it moves on rows nobody edited. */\n updatedAt?: string;\n};\n\n/**\n * One channel action fired when the route matches.\n */\nexport type SensingRouteAction = {\n /** Attach the evidence frame/clip (people-facing channels). */\n attachClip?: boolean;\n /** Selects a strict per-channel shape: `webhook` requires `webhookId`, `whatsapp`/`sms` require `recipientE164`, `email` requires `recipientEmail`, `slack` requires `slackChannelId`, and `clip` needs no destination. Any other destination field on the action is rejected, and `webhook` is the only channel that does not deliver to a person — use it for test routes. */\n channel: \"webhook\" | \"whatsapp\" | \"sms\" | \"slack\" | \"email\" | \"clip\";\n /** E.164 phone number (channel=whatsapp|sms). */\n recipientE164?: string;\n /** Recipient email (channel=email). */\n recipientEmail?: string | null;\n /** Slack channel id (channel=slack). */\n slackChannelId?: string | null;\n /** Message template name (managed WhatsApp/SMS). */\n templateName?: string;\n /** Target webhook endpoint id (channel=webhook). */\n webhookId?: string | null;\n};\n\n/**\n * A threshold condition — the route fires when the sensed field crosses the value.\n */\nexport type SensingRouteCondition = {\n /** The sensed field key (snake_case) to evaluate. */\n field: string;\n /** How the observed value is compared with `value`. `gt`/`gte`/`lt`/`lte` match ONLY when both sides parse as finite numbers; `eq`/`neq` fall back to strict equality for booleans and non-numeric strings. A missing or null observation never matches — including `neq`, which does not fire on absence. */\n op: \"gt\" | \"gte\" | \"lt\" | \"lte\" | \"eq\" | \"neq\";\n /** The threshold value (numeric, string, or boolean depending on the field). */\n value?: number | string | boolean;\n};\n\n/**\n * Threshold-alert timing. NULL preserves legacy behavior; immediate adds no check delay; wait_recheck requires distinct first/follow-up positive images at least reviewWaitSeconds apart on capture time, not uninterrupted activity or message grouping.\n */\nexport type SensingRouteReviewTimingMode = \"immediate\" | \"wait_recheck\" | null | null;\n\n/**\n * Whole seconds before a fresh follow-up check. Required only for wait_recheck; must be NULL for immediate or unspecified timing. These two settings must be updated together.\n */\nexport type SensingRouteReviewWaitSeconds = number | null;\n\n/**\n * A normalized camera area used only by this alert. The rectangle is the compatibility bounding box; optional ordered points define the exact lasso perimeter. Omitted cameras use the whole view.\n */\nexport type SensingRouteScreenFocusRegion = {\n /** Rectangle height as a fraction of frame height, at least 0.05. When `points` is supplied this is DERIVED from the lasso's bounding box. Note the region is only ever sent to the model as prompt text and is not geometrically enforced — measured 2026-08-25, both shapes leaked 100% at every anchor, so a focused alert reacts to the whole frame. */\n height: number;\n /** Ordered non-self-intersecting lasso vertices, normalized to a 0.01 coordinate grid so the saved perimeter is represented exactly in model instructions. The perimeter closes from the last point to the first. */\n points?: Array<{\n x: number;\n y: number;\n }>;\n /** Which of the route's cameras this area applies to. It must appear in the route's explicit `screenMongoIds` and may appear at most once across the array, or the write is a 400; cameras with no entry are watched whole-view. */\n screenMongoId: string;\n /** Rectangle width as a fraction of frame width, at least 0.05. When `points` is supplied this is DERIVED from the lasso's bounding box rather than read from the request, so older readers keep a safe approximation of the polygon. */\n width: number;\n /** Left edge of the bounding rectangle as a fraction of frame width, origin top-left. Stored rounded to 4 decimal places, and capped at 0.95 because `width` has a 0.05 minimum and `x + width` may not exceed 1. */\n x: number;\n /** Top edge of the bounding rectangle as a fraction of frame height, origin top-left. Stored rounded to 4 decimal places, and capped at 0.95 because `height` has a 0.05 minimum and `y + height` may not exceed 1. */\n y: number;\n};\n\nexport type SensingSettingsResponse = {\n /** Summary of the profile row currently deployed and active on this screen (id, name, cadences and derived capabilities). `null` when the screen has never had a profile deployed, in which case the response's mode fields describe the default rather than anything running. */\n active_profile?: {\n capabilities?: Array<string>;\n capture_interval_ms?: number | null;\n profile_id?: string | null;\n profile_name?: string | null;\n report_interval_ms?: number | null;\n } | null;\n desired_mode?: SensingPerformanceMode;\n /** Read-only latest device telemetry used for safety checks. */\n device_capability?: {\n agent_version?: string | null;\n gpu_delegate_supported?: boolean | null;\n gpu_name?: string | null;\n model_tier?: string | null;\n recent_failure_count?: number;\n soc_name?: string | null;\n soc_vendor?: string | null;\n telemetry_fresh?: boolean;\n };\n effective_mode?: SensingPerformanceMode;\n /** Despite the name this is not an apply OUTCOME and carries no device acknowledgement: it is `null` when the screen has no active profile and otherwise always `{ status: \"active_profile_loaded\", profile_id, updated_at }` describing the profile row that is loaded now. */\n last_apply_status?: Record<string, unknown> | null;\n /** The full selectable set with human labels (`auto` = \"Auto recommended\", `presence_only` = \"Viewer count only\", and so on). It is a static list, NOT filtered by this screen's hardware — a mode listed here can still be refused; read `safety.blocks` before offering it. */\n modes?: Array<{\n label?: string;\n value?: SensingPerformanceMode;\n }>;\n /** Hardware pre-flight for `effective_mode`. `allowed` is simply `blocks.length === 0`; `warnings` never block (stale telemetry, recent device failures), while a `block` — missing fresh telemetry for a load-increasing mode, or FLOOR-tier/CPU-only hardware for `advanced` — makes a PUT of that mode answer 409 with this object under `error.details.safety`. */\n safety?: {\n allowed?: boolean;\n blocks?: Array<{\n code?: string;\n message?: string;\n }>;\n warnings?: Array<{\n code?: string;\n message?: string;\n }>;\n };\n /** The screen's Mongo id — the same identifier the route surfaces call `screenMongoId`. Echoes the path parameter. */\n screen_id?: string;\n};\n\n/**\n * Webhook request BODY delivered when a Sense Anything Route fires. Unlike the\n * device/programmatic events, a Route fire delivers a FLAT, self-describing\n * payload (no `{event,timestamp,data}` wrapper) — the event type is in the\n * `X-Trillboards-Event` request header (`sensing.threshold_crossed` for a custom\n * sensed field, or the matching `audience.*` / `venue.*` event for a known\n * semantic; a scheduled-digest Route delivers `venue.traffic_summary`). All Route\n * events share this same body shape. Verify the `X-Trillboards-Signature` header\n * over the raw body before trusting it.\n *\n */\nexport type SensingThresholdCrossedEvent = {\n /** The sensed field key evaluated (null for count-only digests). */\n field?: string | null;\n /** Server clock when the evaluator fired — not when the camera saw it. On the cloud path it was measured 8.2 s median and 15.3 s p95 behind the frame's shutter, so use the payload's `captured_at` (the device's own shutter instant) whenever the fire carried a frame. */\n fired_at?: string;\n /** The observed value that crossed the threshold. */\n observed?: unknown;\n /** The comparison the evaluator applied. `null` for a scheduled digest, which aggregates rather than comparing. Numeric operators only ever match when both the observed value and the threshold parse as finite numbers. */\n op?: \"gt\" | \"gte\" | \"lt\" | \"lte\" | \"eq\" | \"neq\" | null;\n /** Who owns the route that fired. Webhooks are dispatched to the SCREEN's partner regardless of who created the route, so a partner endpoint legitimately receives `earner_user` fires for routes the screen's own operator configured and which are not visible through the Partner API. */\n owner_kind?: \"earner_user\" | \"partner\";\n /** The Route that fired. */\n route_id?: string;\n /** The route's operator-supplied label at the time of the fire. It is mutable and not unique — match on `route_id` for idempotency and routing. */\n route_name?: string;\n /** The screen (mongo id) whose observation crossed the threshold. */\n screen_id?: string;\n /** The configured threshold value (number, string, or boolean). May be null. */\n threshold?: unknown;\n};\n\nexport type SsdpDeviceInfo = {\n /** The `<friendlyName>` element from the fetched description XML. If it looks like a personal name (possessive apostrophe-s, an email, a MAC) the ENTIRE row is discarded rather than the field being blanked, so consumer devices named after their owner never appear at all. */\n friendlyName?: string | null;\n /** The `LOCATION:` header from the M-SEARCH response — the URL of the device description XML, and the de-duplication key for the whole list (one row per distinct LOCATION, last response wins). A response without it produces no row at all. */\n location: string;\n /** The `<manufacturer>` element from the description XML. `null` whenever the description fetch was skipped, timed out or failed — which is common, since fetching is bounded to half the listen window with a 1.5 s per-host timeout — so absence says nothing about the device. */\n manufacturer?: string | null;\n /** The `<modelName>` element from the description XML, field-stripped (not row-dropped) if it contains an email or a MAC. Absent on every row whose description fetch did not complete. */\n modelName?: string | null;\n /** The raw `SERVER:` response header, an RFC 2616 product token such as `Linux UPnP/1.0 Sonos/72.6-31040`. It comes from the M-SEARCH response itself, so unlike the XML-derived fields below it is present even when the description fetch fails — and it is deliberately neither PII-filtered nor hashed server-side, being treated as a low-cardinality vendor hint. */\n server?: string | null;\n /** The `ST:` search-target header of the response, e.g. `urn:schemas-upnp-org:device:ZonePlayer:1`. The probe searches with `ST: ssdp:all`, so this reflects what the device chose to answer as, not what we asked for. It arrives with the M-SEARCH response, so it survives a failed description fetch; an empty string means the responder omitted the header. */\n st: string;\n /** The `<UDN>` element — the device's self-declared Unique Device Name, typically a `uuid:` URN and the most stable per-device key SSDP offers. It is hashed server-side, and it is absent whenever the description fetch did not complete. */\n udn?: string | null;\n [key: string]: unknown;\n};\n\nexport type TeamInviteResponse = {\n /** The invited address, lower-cased and trimmed. Once the invitation is accepted the listing reports the accepting user account's email instead, which may differ from this one. */\n email?: string;\n /** When the invitation expires (7 days from creation) */\n expires_at?: string;\n /** A freshly minted 24-character hex ObjectId held as the membership's legacy id — NOT the `memberships` UUID primary key. It is the value the team routes take as the member path parameter. */\n membership_id?: string;\n /** Only `admin` and `viewer` can be invited — `owner` is the membership created with the partner account and cannot be granted through this endpoint. */\n role?: \"admin\" | \"viewer\";\n /** `active` when the invited email already had a Trillboards account: the membership is auto-accepted at invite time, `accepted_at` is set immediately, no token is issued and `expires_at` is `null`. `pending` otherwise, with a 7-day invite token. Either way the team is capped at 50 active-or-pending members. */\n status?: \"pending\" | \"active\";\n};\n\nexport type TeamMember = {\n /** `null` while `status` is `pending`. Set at INVITE time rather than click time when the invitee already had a Trillboards account and the membership was auto-activated. */\n accepted_at?: string | null;\n /** The linked user account's email once the invitation is accepted; the address the invitation was SENT to while `status` is `pending`. The two differ if the invitee accepts with a different account. */\n email?: string;\n /** When the invitation row was created. Re-inviting the same address rotates the token and moves this timestamp forward. */\n invited_at?: string;\n /** Name and email of the inviting user, resolved by join. `null` for the owner membership created with the partner account, and for any invitation whose inviter row no longer resolves. */\n invited_by?: {\n email?: string;\n name?: string;\n } | null;\n /** **Always `null`.** The column has no counterpart in `memberships` after the tenant-membership cutover, so the canonical reader projects a literal `NULL::timestamptz` and nothing writes it. */\n last_portal_access?: string | null;\n /** Unique membership record ID */\n membership_id?: string;\n /** The linked user account's display name. Undefined — and so omitted from the JSON — while `status` is `pending`, because no user row is attached until the invitation is accepted. */\n name?: string;\n permissions?: RolePermissions;\n /** Profile photo URL from the linked user account, and null/absent while the invitation is pending for the same reason as `name`. */\n photo?: string | null;\n /** `owner` is the membership created with the partner account and can never be invited or granted — only `admin` and `viewer` are grantable. Role alone determines `permissions`. */\n role?: \"owner\" | \"admin\" | \"viewer\";\n /** The listing returns `active` + `pending` only; `revoked` rows appear only when `?status=revoked` is passed explicitly. An invitation to an email that already has a Trillboards account is auto-accepted and lands `active` immediately. */\n status?: \"active\" | \"pending\" | \"revoked\";\n /** User ID (null if pending invitation) */\n user_id?: string;\n};\n\nexport type Transaction = {\n /** Integer US cents. Earnings rows are allocated cumulatively so the list sums EXACTLY to the lifetime total — an individual row can therefore differ by one cent from rounding that day's dollars on its own. */\n amount_cents?: number;\n /** For a `payout`, the real creation timestamp. For an `earning` it is `new Date(period_date)` — midnight UTC of the accounting day, a value derived from the date key rather than the time anything happened. */\n created_at?: string;\n /** For an `earning` row, the `YYYY-MM-DD` accounting period the aggregate covers. For a `payout` row it is the payout record's creation timestamp, so it carries a time component despite the declared `date` format. */\n date?: string;\n earning_source_version?: 1;\n earning_sources?: EarningsSources;\n /** Synthetic and namespaced by origin, never a ledger primary key: earnings rows are `daily_<YYYY-MM-DD>` (one per accounting day, not per impression); payouts are `admin_batch:<ledger_id>`, `earner_withdrawal:<withdrawal_id>` or `legacy_transfer:<id>`. */\n id?: string;\n /** Number of impressions (for earning transactions) */\n impressions?: number;\n /** Source of the transaction (e.g., impressions, payout) */\n source?: string;\n /** Stripe transfer ID (for payout transactions) */\n stripe_transfer_id?: string;\n /** `earning` rows are daily revenue aggregates from `programmatic_earnings_daily`; `payout` rows are money leaving, unioned from three sources and deduplicated by provider transfer id. `?type=all` (the default) merges both and sorts newest-first. */\n type?: \"earning\" | \"payout\";\n};\n\n/**\n * The canonical Trillboards error envelope. Emitted by `middleware/globalErrorHandler.js` for every error that reaches it: `utils/apiErrors.ts` `toJSON()` builds the inner object and the handler wraps it in `{ error }` and stamps `request_id`.\n */\nexport type TrillboardsError = {\n error: {\n /** Stable machine-readable error code. Switch on this, never on `message`. The 143 CATALOGUED codes are published as the `ErrorCode` schema and served live by `GET /v1/errors`, which is the authority. This field is NOT limited to them: services emit further codes directly, and `ApiError.notFound(resource)` mints `<resource>_not_found` from an arbitrary resource name. Always handle an unrecognised code by falling back to `type`. */\n code: string;\n /** Per-field violations (populated from Zod errors). */\n details?: Array<{\n code?: string;\n message?: string;\n param?: string;\n }>;\n /** Documentation for this code — https://api.trillboards.com/docs/errors#<code>. */\n doc_url?: string;\n /** Agent-facing hints and suggestions. */\n help?: {\n portal_url?: string;\n suggestions?: Array<string>;\n summary?: string;\n try_next?: string;\n };\n /** Human-readable error message. Safe to surface to end-users. */\n message: string;\n /** Offending parameter name, or null when the error is not field-scoped. */\n param?: string | null;\n /** Echoed X-Request-Id. Quote this to support for log correlation. */\n request_id: string;\n type: ErrorType;\n };\n};\n\n/**\n * Partial, non-clobbering update — include only the fields to change. Update reviewTimingMode and reviewWaitSeconds together; omit both to preserve existing timing. Explicit timing cannot be added to a digest, and changing to a digest clears the prior event check timing.\n */\nexport type UpdatePartnerSensingRouteRequest = {\n /** REPLACES the entire action list (1..10 entries); the existing rows are deleted and reinserted. Omit the key to leave actions untouched — there is no way to add or remove a single action. */\n actions?: Array<SensingRouteAction>;\n /** Seven-bit weekday mask evaluated in `activeTz`: bit 0 is Monday through bit 6 is Sunday, so 127 is every day and 0 never fires. Requires `activeTz`, like the rest of the active window. */\n activeDaysMask?: number;\n /** Inclusive end of the daily active window, minutes past local midnight in `activeTz` (0..1439). A value below `activeStartMinute` is read as an overnight window, not an empty one. */\n activeEndMinute?: number;\n /** Inclusive start of the daily active window, minutes past local midnight in `activeTz` (0..1439). Requires `activeTz` to have any effect. */\n activeStartMinute?: number;\n /** IANA zone name the active window is evaluated in. Setting the minute/day fields without this leaves the window inert, and an unparseable zone fails open (always active) rather than suppressing alerts. */\n activeTz?: string;\n condition?: SensingRouteCondition;\n /** Minimum gap in seconds between alerts for the same (route, screen) episode, clamped to 0..86400. `0` means fire on every rising edge with no spacing at all. */\n debounceSeconds?: number;\n /** Replaces the digest block and, as a side effect, rewrites `condition` to `{ field }` or `null`. `field` is required for `avg` and `max`; `intervalMinutes` must be at least 5 because the digest cron only runs every 5 minutes. */\n digest?: {\n field?: string;\n intervalMinutes?: number;\n metric?: \"count\" | \"avg\" | \"max\";\n windowMinutes?: number;\n };\n /** Only the JSON literal `true` enables the route here — every other value, including the string `\"true\"` and `1`, is written as `false`. This is the mirror image of create, where only the literal `false` disables, so send a real boolean. */\n enabled?: boolean;\n /** Replaces the label. Must be a non-empty string after trimming and at most 200 characters; sending an empty or whitespace-only name is a 400 rather than a clear. */\n name?: string;\n /** Rebinds the route to a different named sensing profile; send `null` (or an empty/whitespace string) to unbind it. Always a profile NAME — a `profile_id` is re-minted on every redeploy and would orphan the binding. */\n profileName?: string | null;\n reviewTimingMode?: SensingRouteReviewTimingMode;\n reviewWaitSeconds?: SensingRouteReviewWaitSeconds;\n /** Replace all selected rectangles or polygon lassos for the route; send [] to return every camera to whole view. Entries must target the route's explicit screenMongoIds. */\n screenFocusRegions?: Array<SensingRouteScreenFocusRegion>;\n /** REPLACES the watched screen set (1..250, deduped); mutually exclusive with `targetGroupId` in one request. Focus regions for cameras that survive the swap are PRESERVED unless you also send `screenFocusRegions` (including `[]` to clear them). */\n screenMongoIds?: Array<string>;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainMinSamples?: number;\n /** How long the condition must hold before the FIRST alert, in seconds (600 = ten minutes). Omit to alert on the first true reading. Cadence-independent, because the evaluator compares timestamps - correct however often the screen reports, including when that rate changes while the condition holds. */\n sustainSeconds?: number;\n /** DEPRECATED and rejected on write with 400. A duration expressed as N readings inside a fixed window can only be satisfied by a screen reporting faster than window/(N-1) seconds, and nothing knows that rate when the route is saved - so the route saves cleanly and then never fires at all. Send sustainSeconds instead. Still honoured for rows that already carry it. */\n sustainWindowSeconds?: number;\n /** Set to a sg_ group id to (re)target the group's live members, or null to switch back to an explicit screenMongoIds set. Mutually exclusive with screenMongoIds in one request. */\n targetGroupId?: string | null;\n};\n\n/**\n * The resolved programmatic VAST waterfall. `sources` is an ORDERED ARRAY and clients MUST iterate ALL of it — by `priority` ascending, skipping `enabled: false` — advancing to the next source on no-fill, empty VAST, timeout, IMA load error AND playback error (VAST 303 / 300 / 301 / 1009 all mean \"try the next source\", never \"stop\"). Reading `sources[0]` and stopping is the most common integration defect and reads as \"no demand\" when demand exists further down. Apply each source's own `timeout_ms`, and cap the whole loop at `total_timeout_ms`. Always handle player error and never leave the screen black; always fire the VAST `<Error>` beacon with `[ERRORCODE]` substituted. Full contract: docs/integrations/partner-native-ima-sdk.md §0 and §4.1. SQL/Admin-enabled sources remain visible; request-pressure decisions are enforced, when enabled, at first-party upstream fetch seams rather than by removing sources from this catalog.\n *\n */\nexport type VastWaterfall = {\n /** Derived, not configured: it is simply `sources.length > 0`. `false` means no demand source resolved for this screen on this request, so fall back to your own content. It is not a partner-settable toggle and it does not mean programmatic serving is switched off for the screen. */\n enabled?: boolean;\n /** Every source must be attempted, by `priority` ascending, until one actually plays. Not a preference list. */\n sources?: Array<VastWaterfallSource>;\n /** Budget in ms for the ENTIRE waterfall loop, across all sources. When it is spent, stop trying sources and return to your own content. */\n total_timeout_ms?: number;\n} | null;\n\n/**\n * One demand source in the VAST waterfall. Fetch `vast_url` (already a Trillboards vast-wrap URL — use as-is) and pass the response to your IMA SDK. A failure here means \"advance to the next source\", not \"the request had no demand\".\n */\nexport type VastWaterfallSource = {\n /** Identical across every source in this response — one id per waterfall build, so it groups all of a single ad opportunity's source attempts, while `request_id` identifies this source alone. Echo it back; never mint your own. */\n auction_request_id?: string;\n /** The source's effective FLOOR CPM in dollars per thousand impressions (never cents) — the configured tag floor (`NUMERIC(8,2)`) or the partner's default, after any demand price action. It is an expectation used to order the waterfall, not a cleared price and not what the screen earns; `null` when the partner has no floor configured. */\n cpm?: number | null;\n /** High recent error rate measured server-side. Degraded sources are already pushed to the tail of the array for you — do NOT skip a source because of this flag. */\n degraded?: boolean;\n /** Whole seconds of the creative THIS source will serve, so you can schedule continuous loop playback without first fetching the VAST and probing the MP4. Present only on the first-party `trillboards_direct` source (a programmatic source's length is not known until its own VAST is fetched). It is the same number as the `<Duration>` in the VAST that `vast_url` returns, so use either — they cannot disagree. NOT a playback control: IMA still takes its real timing from the media, so treat this as scheduling metadata and keep honouring the actual quartile/complete events. A creative longer than your ad slot will still be cut off by the next poll; we warn the uploader at upload time when that is the case. */\n duration_seconds?: number;\n /** When false, skip this source entirely. Present-but-disabled sources may appear in the array. */\n enabled?: boolean;\n /** Recent non-response rate for this demand source as a PERCENT 0-100 (not a 0-1 fraction). It is fleet-wide rather than per-screen, derived from an hourly rollup that can be ~15 minutes stale, and it is present only on sources that also carry `degraded: true`. Advisory: it never means skip the source. */\n error_rate_pct?: number;\n /** No live producer — `null` on every source. The only assignment is guarded on a partner-level `fetchMode` setting that no demand partner in the registry defines. Do not branch on it. */\n fetch_mode?: string | null;\n /** Opaque public alias for the demand source. Echo it back as `variant_name` on `programmatic-event` reports. */\n name?: string;\n /** Opaque ascending sort key — lowest is tried first. Do NOT treat it as an index: it may start at 0 (a source we want to win outright — the `?test=1` source, an injected first-party bid, an auction winner — is stamped `priority: 0`), and values may be non-contiguous (some paths renumber survivors 1..N, others keep catalog values). Only the relative order is meaningful; sort ascending and do no arithmetic on it. */\n priority?: number;\n /** Per-source request identifier. Report against the source you actually used, not `sources[0]`. */\n request_id?: string;\n /** Per-source budget in ms for fetching and loading THIS source. Distinct from the waterfall-wide `total_timeout_ms`. */\n timeout_ms?: number;\n /** Fetch verbatim. The server has already appended this source's `request_id` and rewritten every partner-identifying path segment and query parameter to an opaque alias, so editing, re-ordering or re-signing the query breaks impression attribution. A `data:` URL can appear here for an inbound-DSP source that carries its VAST inline. */\n vast_url?: string;\n};\n\nexport type Webhook = {\n /** When webhook was created */\n created_at?: string;\n /** Subscribed event types */\n events?: Array<\"device.online\" | \"device.offline\" | \"impression.recorded\" | \"creative.rewards_ready\" | \"campaign.allocated\" | \"payout.processed\" | \"sensing.threshold_crossed\" | \"audience.spike\" | \"audience.venue_busy\" | \"audience.purchase_intent\" | \"audience.demographics_update\" | \"venue.traffic_summary\" | \"screen_group.created\" | \"screen_group.updated\" | \"screen_group.deleted\" | \"screen_group.member.added\" | \"screen_group.member.removed\" | \"screen_group.content_policy.updated\" | \"screen_group.sensing_config.updated\" | \"fleet_command.completed\" | \"fleet_command.failed\">;\n /** Total failed deliveries */\n failure_count?: number;\n /** Last time webhook was triggered */\n last_triggered_at?: string;\n /** Webhook signing secret (whsec_…). Returned when the webhook is created so partners can verify the compound `X-Trillboards-Signature` header over the raw payload (see the SDK's Webhooks.constructEvent). Treat as a credential — store it securely and never expose it client-side. */\n secret?: string;\n /** Webhook status */\n status?: \"active\" | \"inactive\";\n /** Total successful deliveries */\n success_count?: number;\n /** Webhook endpoint URL */\n url?: string;\n /** Unique webhook identifier */\n webhook_id?: string;\n};\n\nexport type WebhookDelivery = {\n /** When delivery was first attempted */\n attempted_at?: string;\n /** When delivery succeeded */\n delivered_at?: string;\n /** Error message if failed */\n error_message?: string;\n /** Event that triggered this delivery */\n event_type?: string;\n /** HTTP status code from endpoint */\n http_status?: number;\n /** Delivery record ID */\n id?: string;\n /** Response time in milliseconds */\n response_time_ms?: number;\n /** Number of retry attempts */\n retry_count?: number;\n /** Delivery status */\n status?: \"pending\" | \"success\" | \"failed\" | \"retrying\";\n};\n\n/**\n * Standard webhook event payload sent to your endpoint.\n * All events follow this structure.\n *\n */\nexport type WebhookEvent = {\n /** Event-specific data */\n data?: Record<string, unknown>;\n /** Event type */\n event?: string;\n /** When event occurred */\n timestamp?: string;\n};\n\nexport type WifiEnvironmentSnapshot = {\n /** Dimensionless 0.0-1.0: visible APs whose centre frequency is within ±10 MHz of the joined AP, divided by ALL visible APs in the capped list. Because the joined AP is itself in the numerator it is never 0 while connected — a literal `0.0` means not connected (or an empty scan), not an empty channel. Above ~0.5 indicates a dense venue, below ~0.2 an isolated one. */\n channelCongestionRatio?: number | null;\n /** Channel width of the joined AP in MHz — but it is APPROXIMATE by construction: the producer returns the width of the FIRST entry in the scan list, which is only usually the joined AP, rather than matching on BSSID. `null` when not connected, on Android below API 23, or for an 80+80 MHz AP, which the width mapping does not cover. */\n connectedChannelWidthMhz?: number | null;\n /** Centre frequency of the joined AP's primary 20 MHz channel, in MHz. `0` means not connected / unavailable, and that zero also zeroes `channelCongestionRatio` and forces `frequencyBand` to `unknown`. */\n connectedFrequencyMhz?: number | null;\n /** Negotiated PHY link speed of the joined AP in Mbps (`WifiInfo.getLinkSpeed()`) — a link-rate ceiling, not measured throughput. `null` rather than 0 when not connected or when the platform reports a non-positive value. */\n connectedLinkSpeedMbps?: number | null;\n /** RSSI of the JOINED access point in dBm. `-100` is a SENTINEL, not a measurement: it is the literal default when `WifiInfo` is unavailable or the device is not on Wi-Fi, so a genuinely weak link and no link at all are indistinguishable here. */\n connectedSignalDbm?: number | null;\n /** Band of the JOINED AP: `2.4ghz`, `5ghz`, `6ghz` or `unknown`. It is derived from `connectedFrequencyMhz`, so a device that is not on Wi-Fi reports `unknown` rather than null — and the 5900-5924 MHz range falls into `unknown` too, because the classifier's 5 GHz and 6 GHz windows do not meet. */\n frequencyBand?: string | null;\n /** Median RSSI in dBm across ALL visible APs (not just the joined one). It is the UPPER median for an even-sized list — the two middle values are not averaged — and falls back to the `-100` sentinel on an empty list. */\n medianSignalDbm?: number | null;\n /** Distinct BSSIDs in the scan, after the 50-entry cap. It is literally the SAME value as `uniqueBssidCount` — both are assigned from one field — so the two never disagree and neither is a count of the uncapped scan. */\n networkCount?: number | null;\n /** Population VARIANCE of the joined AP's RSSI in dBm SQUARED — not a standard deviation, and the divisor is N, not N-1. The window is the last 5 HEARTBEAT cycles held in a process-lifetime buffer, not the current scan, so it reads `0.0` on the first cycle after an agent restart; the `-100` not-connected sentinel is admitted into that history and skews it. Intended as a coarse motion indicator, not occupancy. */\n rssiVariance?: number | null;\n /** UNIX EPOCH milliseconds (`System.currentTimeMillis()`) stamped when the scan COMPLETED — wall clock, not boot-relative, and not the scan start. It is a different clock from Android's own per-result `ScanResult.timestamp`, which is microseconds since boot and never reaches the wire. */\n scanTimestampMs?: number | null;\n /** Strongest minus weakest visible RSSI, in dB — a positive difference, not an absolute level. It is `0` when fewer than two APs are visible, which is indistinguishable from a genuinely uniform RF environment. Used as a venue-openness hint. */\n signalSpreadDbm?: number | null;\n /** Distinct BSSIDs after de-duplication and the 50-entry cap — the identical value to `networkCount` above, assigned from the same field, so it adds no independent information. */\n uniqueBssidCount?: number | null;\n [key: string]: unknown;\n} | null;\n\nexport type WifiScanResult = {\n /** Bonded channel width in MHz, mapped from Android's width enum to a literal 20, 40, 80 or 160. `null` on Android below API 23, on any read failure, AND for an 80+80 MHz AP — that variant has no mapping, so `null` does not mean narrow. */\n channelWidthMhz?: number | null;\n /** Centre frequency of the AP's PRIMARY 20 MHz channel in MHz (2400-7200 accepted, covering 2.4/5/6 GHz) — not the centre of a wider bonded channel. Convert to a channel number yourself; the wire carries only the frequency. */\n frequencyMhz: number;\n /** The AP's BSSID exactly as Android reports it, unhashed on the device; Trillboards stores it as received and as a keyed HMAC-SHA256 hash. Rows are de-duplicated by BSSID keeping the strongest reading, and the literal `02:00:00:00:00:00` — Android's redaction placeholder for a caller without location permission — is dropped rather than forwarded, so a permission-less device produces an empty list, not a list of placeholders. */\n rawBssid: string;\n /** AP signal level in dBm (negative, bounded -120..0), from `ScanResult.level`. Note the wire key the Android agent actually sends is `signal_dbm`, not a snake_case form of this name — a mismatch of exactly this kind left the ClickHouse `rssi` column NULL on 100% of Wi-Fi rows until 2026-08-08. */\n signalStrengthDbm: number;\n [key: string]: unknown;\n};\n\n\n// ─── Operations ──────────────────────────────────────────────────────────\n\n/**\n * Query API changelog\n * GET /v1/changelog\n */\nexport interface queryChangelogOperation {\n readonly path: \"/v1/changelog\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** Only entries dated on or after this date (YYYY-MM-DD). Unreleased entries always included. */\n since?: string;\n /** Filter by change category: \"breaking\" (changed+removed), \"additive\" (added), \"deprecation\" (deprecated), \"fix\" (fixed). Comma-separated for multiple. */\n type?: string;\n};\n response: ChangelogResponse;\n}\n\n/**\n * Ad-sighting records: outdoor ads our cab cameras read, each with a signed photo link.\n * GET /v1/data/ad-sightings\n */\nexport interface dataApiAdSightingsOperation {\n readonly path: \"/v1/data/ad-sightings\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** A board the sighting was matched to, repeatable. */\n board_id?: Array<string> | null;\n /** A brand, repeatable for a brand set (competitors). Matched however it is typed: \"McDonald's\", \"mcdonalds\" and \"Mc Donalds\" find the same records. */\n brand?: Array<string> | null;\n /** A resolved brand id, repeatable. Combined with `brand` as either-or. */\n brand_id?: Array<string> | null;\n /** A sighted creative (the campaign artwork), repeatable. */\n creative_id?: Array<string> | null;\n /** The `next_cursor` of the previous page. */\n cursor?: string;\n /** Start (inclusive), YYYY-MM-DD or an ISO-8601 instant. Default: 30 days before `to`. */\n from?: string;\n /** The reading instrument (ssr-v2, ssr-v1, vlmtext-v1, edge_ocr_v1), repeatable. */\n instrument?: Array<string> | null;\n /** Records per page, 1..500 (default 50). The page limit is reserved; the records returned are billed. */\n limit?: number;\n /** A market as COUNTRY:CODE (US DMA or CA CMA), repeatable. */\n market?: Array<string> | null;\n /** The surface kind as read (billboard, storefront, bus_shelter, ...), repeatable. */\n surface?: Array<string> | null;\n /** End (exclusive). Default: now. The span is at most 400 days. */\n to?: string;\n};\n response: DataApi_AdSightingsResponse;\n}\n\n/**\n * Outdoor creative history by brand, market and month, from the creative ledger.\n * GET /v1/data/ad-sightings/history\n */\nexport interface dataApiAdSightingsHistoryOperation {\n readonly path: \"/v1/data/ad-sightings/history\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** A brand by name, repeatable (competitors). Resolved to its brand id the way the brand search does. */\n brand?: Array<string> | null;\n /** A resolved brand id, repeatable. */\n brand_id?: Array<string> | null;\n /** The `next_cursor` of the previous page. */\n cursor?: string;\n /** First month (YYYY-MM) or day. Default: 12 months before `to`. */\n from?: string;\n /** market_month (default) or fleet_month. */\n grain?: \"market_month\" | \"fleet_month\";\n /** Rows per page, 1..500 (default 100). The page limit is reserved; the rows returned are billed. */\n limit?: number;\n /** A market as COUNTRY:CODE, repeatable. */\n market?: Array<string> | null;\n /** Last month (YYYY-MM, inclusive) or day. Default: this month. */\n to?: string;\n};\n response: DataApi_AdSightingsHistoryResponse;\n}\n\n/**\n * Aggregate audience analytics for a time window.\n * GET /v1/data/aggregate\n */\nexport interface dataApiAggregateOperation {\n readonly path: \"/v1/data/aggregate\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** Dimension to group aggregations by. */\n group_by?: \"venue_type\" | \"daypart\" | \"day_of_week\" | \"income_estimated_level\" | \"primary_mood\";\n /** Metric to aggregate on. */\n metric?: \"avg_face_count\" | \"avg_attention\" | \"income_confidence\" | \"max_face_count\";\n /** Relative time window (e.g. \"24h\", \"7d\"). */\n since?: string;\n};\n response: DataApi_AggregateResponse;\n}\n\n/**\n * Find screens with similar audience profiles.\n * GET /v1/data/lookalike\n */\nexport interface dataApiLookalikeScreensOperation {\n readonly path: \"/v1/data/lookalike\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n min_similarity?: number;\n /** Internal screen identifier. */\n screen_id: string;\n};\n response: DataApi_LookalikeResponse;\n}\n\n/**\n * Composable query DSL over approved datasets.\n * POST /v1/data/query\n */\nexport interface dataApiQueryDslOperation {\n readonly path: \"/v1/data/query\";\n readonly method: \"POST\";\n request: DataApi_QueryDslBody;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: DataApi_QueryDslResponse;\n}\n\n/**\n * List screens visible to the caller.\n * GET /v1/data/screens\n */\nexport interface dataApiListScreensOperation {\n readonly path: \"/v1/data/screens\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** ISO-3166 alpha-2 country code. */\n country?: string;\n limit?: number;\n offset?: number;\n /** Filter by venue taxonomy type (e.g. bar, restaurant). */\n venue_type?: string;\n};\n response: DataApi_ScreensListResponse;\n}\n\n/**\n * Real-time audience snapshot for a screen.\n * GET /v1/data/screens/{screenId}/audience\n */\nexport interface dataApiScreenAudienceLiveOperation {\n readonly path: \"/v1/data/screens/{screenId}/audience\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** Internal screen identifier. */\n screenId: string;\n};\n query: Record<string, unknown>;\n response: DataApi_ScreenAudienceResponse;\n}\n\n/**\n * Historical audience_metrics rows for a screen.\n * GET /v1/data/screens/{screenId}/history\n */\nexport interface dataApiScreenAudienceHistoryOperation {\n readonly path: \"/v1/data/screens/{screenId}/history\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** Internal screen identifier. */\n screenId: string;\n};\n query: {\n limit?: number;\n /** ISO-8601 start timestamp. */\n since?: string;\n /** ISO-8601 end timestamp. */\n until?: string;\n};\n response: DataApi_ScreenHistoryResponse;\n}\n\n/**\n * Similarity search over scene embeddings.\n * GET /v1/data/semantic-search\n */\nexport interface dataApiSemanticSearchOperation {\n readonly path: \"/v1/data/semantic-search\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n min_similarity?: number;\n q: string;\n /** Relative time window (e.g. \"24h\", \"7d\"). */\n since?: string;\n};\n response: DataApi_SemanticSearchResponse;\n}\n\n/**\n * Platform-wide counters.\n * GET /v1/data/stats\n */\nexport interface dataApiStatsOperation {\n readonly path: \"/v1/data/stats\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: DataApi_StatsResponse;\n}\n\n/**\n * Server-Sent Events stream of live audience/observation events.\n * GET /v1/data/stream\n */\nexport interface dataApiStreamOperation {\n readonly path: \"/v1/data/stream\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n observation_family?: string;\n venue_type?: string;\n};\n response: unknown;\n}\n\n/**\n * API usage for the authenticated key (billing dashboard).\n * GET /v1/data/usage\n */\nexport interface dataApiUsageOperation {\n readonly path: \"/v1/data/usage\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n days?: number;\n};\n response: DataApi_UsageResponse;\n}\n\n/**\n * List all error codes\n * GET /v1/errors\n */\nexport interface listErrorCodesOperation {\n readonly path: \"/v1/errors\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: ErrorListResponse;\n}\n\n/**\n * Look up a single error code\n * GET /v1/errors/{code}\n */\nexport interface getErrorCodeOperation {\n readonly path: \"/v1/errors/{code}\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** A stable error code from the catalog. See GET /v1/errors for the full list. */\n code: string;\n};\n query: Record<string, unknown>;\n response: ErrorCodeResponse;\n}\n\n/**\n * Example healthcheck (Phase 3 pipeline proof)\n * GET /v1/example/healthcheck\n */\nexport interface exampleHealthcheckOperation {\n readonly path: \"/v1/example/healthcheck\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n /** When `true`, include diagnostic fields in the response. */\n verbose?: \"true\" | \"false\";\n};\n response: DataApi_ExampleHealthcheckResponse;\n}\n\n/**\n * List self-promo ads\n * GET /v1/partner/ads/self-promo\n */\nexport interface listPartnerSelfPromoAdsOperation {\n readonly path: \"/v1/partner/ads/self-promo\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: Array<PartnerSelfPromoAd>;\n success?: boolean;\n};\n}\n\n/**\n * Create self-promo ad (URL-based)\n * POST /v1/partner/ads/self-promo\n */\nexport interface createPartnerSelfPromoAdOperation {\n readonly path: \"/v1/partner/ads/self-promo\";\n readonly method: \"POST\";\n request: PartnerSelfPromoCreate;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: PartnerSelfPromoAd;\n success?: boolean;\n warnings?: PartnerCreativeWarnings;\n};\n}\n\n/**\n * List self-promo placements by screen\n * GET /v1/partner/ads/self-promo/placements\n */\nexport interface listPartnerSelfPromoPlacementsOperation {\n readonly path: \"/v1/partner/ads/self-promo/placements\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: Array<PartnerSelfPromoPlacement>;\n success?: boolean;\n};\n}\n\n/**\n * Schedule existing self-promo ad to screens\n * POST /v1/partner/ads/self-promo/schedule\n */\nexport interface schedulePartnerSelfPromoOperation {\n readonly path: \"/v1/partner/ads/self-promo/schedule\";\n readonly method: \"POST\";\n request: {\n ad_id: string;\n end_date?: string;\n screen_ids: Array<string>;\n start_date?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Create self-promo ad (upload)\n * POST /v1/partner/ads/self-promo/upload\n */\nexport interface createPartnerSelfPromoUploadOperation {\n readonly path: \"/v1/partner/ads/self-promo/upload\";\n readonly method: \"POST\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: PartnerSelfPromoAd;\n success?: boolean;\n warnings?: PartnerCreativeWarnings;\n};\n}\n\n/**\n * Disable a self-promo ad\n * POST /v1/partner/ads/self-promo/{ad_id}/disable\n */\nexport interface disableSelfPromoAdOperation {\n readonly path: \"/v1/partner/ads/self-promo/{ad_id}/disable\";\n readonly method: \"POST\";\n request: never;\n params: {\n ad_id: string;\n};\n query: Record<string, unknown>;\n response: {\n ad_id?: string;\n status?: \"disabled\";\n};\n}\n\n/**\n * Register an AI agent\n * POST /v1/partner/agent/register\n */\nexport interface registerAgentOperation {\n readonly path: \"/v1/partner/agent/register\";\n readonly method: \"POST\";\n request: {\n /** Type of agent being registered */\n agent_type: \"dsp_agent\" | \"ssp_agent\" | \"trading_desk\" | \"analytics_platform\" | \"developer\";\n /** Company name (optional) */\n company?: string;\n /** Brief description of the agent's purpose */\n description?: string;\n /** Contact email */\n email: string;\n /** Agent or company name */\n name: string;\n /** Mint an AdCP **sandbox account** (`account.sandbox` in `get_adcp_capabilities`). Media buys made with the returned key validate end to end — screen resolution, published CPM/floor, flight parsing, derived impression goal, every error path — and return a well-formed response marked `sandbox: true`, but persist **no advertisements and no placements**. Nothing can serve, deliver, bill, or pay a venue. Use this for conformance and no-spend testing; register without it for a live account. */\n sandbox?: boolean;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n /** The AdCP **account** this key is bound to. Distinct from the `sandbox` block below, which describes auto-provisioned test SCREENS (supply); this is the account-level buying contract (demand) declared as `account.sandbox` in `get_adcp_capabilities`. */\n account?: {\n /** Seller-assigned account id. Same value as `partner_id`. */\n account_id?: string;\n note?: string;\n /** True when this is a sandbox account: its media buys write no advertisements and no placements, and every media-buy tool response is marked `sandbox: true`. */\n sandbox?: boolean;\n };\n agent_type?: string;\n /** API key for authentication (display once — save it!) */\n api_key?: string;\n /** MCP tools available to this agent */\n available_tools?: Array<string>;\n /** Exact same-key handoff for buyer/OpenRTB control-plane capabilities. */\n control_plane_onboarding?: {\n authorization?: string;\n endpoint?: string;\n method?: string;\n purpose?: string;\n reuses_api_key?: boolean;\n };\n docs_url?: string;\n /** Free tier limits per product */\n free_tiers?: Record<string, unknown>;\n mcp_endpoint?: string;\n mcp_transport?: string;\n /** Guidance for getting started */\n next_steps?: Array<string>;\n object?: string;\n /** Unique partner ID */\n partner_id?: string;\n pricing_url?: string;\n request_id?: string;\n /** Auto-provisioned test SCREENS (supply side). Unrelated to `account.sandbox`. */\n sandbox?: {\n note?: string;\n screens?: Array<{\n device_id?: string;\n name?: string;\n venue_type?: string;\n }>;\n };\n tools_url?: string;\n};\n}\n\n/**\n * Get partner analytics\n * GET /v1/partner/analytics\n */\nexport interface getAnalyticsOperation {\n readonly path: \"/v1/partner/analytics\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n end_date?: string;\n start_date?: string;\n};\n response: {\n data?: {\n devices_by_status?: Array<Record<string, unknown>>;\n impressions_by_day?: Array<{\n /** Date (YYYY-MM-DD) */\n _id?: string;\n impressions?: number;\n }>;\n partner?: {\n active_devices?: number;\n pending_payout_cents?: number;\n total_devices?: number;\n total_impressions?: number;\n total_revenue_cents?: number;\n };\n period?: {\n end?: string;\n start?: string;\n };\n };\n success?: boolean;\n};\n}\n\n/**\n * Visitor-flow continuity graph across the partner's screens\n * GET /v1/partner/analytics/audience-continuity\n */\nexport interface getAudienceContinuityOperation {\n readonly path: \"/v1/partner/analytics/audience-continuity\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n windowMinutes?: number;\n};\n response: {\n data?: {\n confidence?: string;\n sampleSize?: number;\n topPaths?: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * Audience composition for the partner's screens\n * GET /v1/partner/analytics/audience-profile\n */\nexport interface getAudienceProfileOperation {\n readonly path: \"/v1/partner/analytics/audience-profile\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n sampleSize?: number;\n segments?: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * BLE device discovery summary\n * GET /v1/partner/analytics/ble-summary\n */\nexport interface getBleSummaryOperation {\n readonly path: \"/v1/partner/analytics/ble-summary\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n totalObservations?: number;\n uniqueDevices?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Content performance — ad lineage and measured audience reactions\n * GET /v1/partner/analytics/content-performance\n */\nexport interface getContentPerformanceOperation {\n readonly path: \"/v1/partner/analytics/content-performance\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n groupBy?: \"canonical\" | \"wrapper\";\n includeUnclassified?: boolean;\n limit?: number;\n offset?: number;\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n byScreen?: Record<string, unknown>;\n creativeResponse?: Array<{\n adId?: string;\n advertiserName?: string;\n ageRating?: string;\n attentiveSeconds?: number;\n audioTerms?: Array<string>;\n avgAttention?: number;\n brandName?: string;\n confidence?: number;\n firstSeen?: string;\n iabCategories?: Array<string>;\n lastSeen?: string;\n moderationStatus?: string;\n plays?: number;\n viewers?: number;\n }>;\n /** Whether another page of creativeResponse exists after this one. */\n has_more?: boolean;\n nextOffset?: number | null;\n topAdvertisers?: Array<Record<string, unknown>>;\n /** Exact number of cards the cursor walks: the feed statement's count over every filter, taken before the page slice. Every page holds exactly `limit` cards except the last, so a walk at any page size reaches the end in ceil(total / limit) pages, and `nextOffset`/`has_more` describe the same set. Null when the feed could not be read (`degraded_fields` then lists `creativeResponse`). Describes the programmatic cursor; the offset-0 all-content block is additive. */\n total?: number | null;\n };\n status?: boolean;\n};\n}\n\n/**\n * Free-text search across the partner's creative catalogue\n * GET /v1/partner/analytics/creative-catalog\n */\nexport interface searchCreativeCatalogOperation {\n readonly path: \"/v1/partner/analytics/creative-catalog\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n offset?: number;\n q?: string;\n};\n response: {\n data?: {\n creatives?: Array<{\n adId?: string;\n advertiserName?: string;\n assetUrl?: string | null;\n brandName?: string;\n iabCategories?: Array<string>;\n moderationStatus?: string;\n }>;\n /** Whether another page of results exists after this one. */\n has_more?: boolean;\n nextOffset?: number | null;\n /** Always null for catalogue search: the read over-fetches limit+1 for a free `has_more`, but an exact total would need a second COUNT round-trip the search path deliberately avoids. Page to completion via `has_more`. */\n total?: number | null;\n };\n status?: boolean;\n};\n}\n\n/**\n * Everything your screens measured while a creative played\n * GET /v1/partner/analytics/creative-observations\n */\nexport interface getCreativeObservationsOperation {\n readonly path: \"/v1/partner/analytics/creative-observations\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n adId?: string;\n adIds?: string;\n contentSha256?: string;\n creativeId?: string;\n exact?: boolean;\n families?: string;\n from?: string;\n identity?: string;\n playLimit?: number;\n screenId?: string;\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\" | \"historical\";\n to?: string;\n};\n response: {\n code?: number;\n data?: {\n /** One rollup per creative (identity_key), every family with its count. */\n creatives?: Array<Record<string, unknown>>;\n /** Families that could not be read this time (their values are null). Absent when every family answered. */\n degraded_fields?: Array<string>;\n /** Why each family in degraded_fields was not read, keyed by family: `read_failed` (with its `code`); `pg_budget` for verified attention read on its Postgres home that did not finish within its budget (`budget_ms`), with the plays it read (`plays_read_n` of `plays_total_n`, `page_plays_read_n` of `page_plays_total_n`; a part read is never returned as the whole); `over_play_limit` with `limit` and `plays_total_n`. Absent when every family answered. */\n degraded_reasons?: Record<string, {\n budget_ms?: number;\n code?: string;\n limit?: number;\n page_plays_read_n?: number;\n page_plays_total_n?: number;\n plays_read_n?: number;\n plays_total_n?: number;\n reason: \"read_failed\" | \"pg_budget\" | \"over_play_limit\";\n }>;\n families?: Array<string>;\n /** The keys read, whether the read was exact, and the files and canonicals they resolved to. */\n identity?: Record<string, unknown>;\n /** The newest plays, each with its creative and every family read for it. */\n plays?: Array<Record<string, unknown>>;\n /** Every play of the creatives in the window, programmatic and supplied. */\n plays_total?: number;\n screen_ids?: Array<string>;\n /** The supplied plays read (plays_n, evidence_plays_n); null when the family was not read. */\n supplied?: Record<string, unknown> | null;\n window?: {\n from?: string;\n to?: string;\n };\n };\n message?: string;\n status?: boolean;\n};\n}\n\n/**\n * Scene-level observations for a played creative\n * GET /v1/partner/analytics/creative-scenes\n */\nexport interface getCreativeScenesOperation {\n readonly path: \"/v1/partner/analytics/creative-scenes\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n adId?: string;\n cursor?: string;\n exact?: boolean;\n identity?: string;\n limit?: number;\n screenId?: string;\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n /** The adId read; absent on an identity read. */\n adId?: string;\n /** The identity keys read (an identity read only). */\n identity?: string;\n nextCursor?: string | null;\n scenes?: Array<Record<string, unknown>>;\n /** The screen read; absent on an identity read across all your screens. */\n screenId?: string;\n timeRange?: string;\n total?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Per-pair flow directionality + transition heatmap\n * GET /v1/partner/analytics/cross-screen-flow\n */\nexport interface getCrossScreenFlowOperation {\n readonly path: \"/v1/partner/analytics/cross-screen-flow\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n edges?: Array<Record<string, unknown>>;\n screens?: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * Unified portfolio dashboard (earnings, ads, audience, screens, insights)\n * GET /v1/partner/analytics/dashboard\n */\nexport interface getPartnerAnalyticsDashboardOperation {\n readonly path: \"/v1/partner/analytics/dashboard\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n endDate?: string;\n period?: \"7d\" | \"30d\" | \"90d\" | \"custom\";\n startDate?: string;\n};\n response: {\n data?: {\n ads?: Record<string, unknown>;\n audience?: Record<string, unknown>;\n earnings?: {\n change?: Record<string, unknown>;\n current?: Record<string, unknown>;\n previous?: Record<string, unknown>;\n sparkline?: Array<Record<string, unknown>>;\n };\n insights?: Array<Record<string, unknown>>;\n period?: {\n endDate?: string | null;\n label?: string;\n startDate?: string | null;\n };\n screens?: Record<string, unknown>;\n topScreens?: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * Device-type / OS / browser-class distribution among repeat visitors\n * GET /v1/partner/analytics/device-suite-signature\n */\nexport interface getDeviceSuiteSignatureOperation {\n readonly path: \"/v1/partner/analytics/device-suite-signature\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n deviceTypes?: Array<Record<string, unknown>>;\n totalDevices?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * CSV export of partner-scope analytics (summary / daily / per-screen)\n * GET /v1/partner/analytics/export\n */\nexport interface exportPartnerAnalyticsOperation {\n readonly path: \"/v1/partner/analytics/export\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n endDate?: string;\n period?: \"7d\" | \"30d\" | \"90d\" | \"custom\";\n startDate?: string;\n type?: \"summary\" | \"daily-breakdown\" | \"screen-breakdown\";\n};\n response: unknown;\n}\n\n/**\n * Per-ad daily breakdown for the partner fleet\n * GET /v1/partner/analytics/export/ads-by-day\n */\nexport interface exportPartnerAdsByDayOperation {\n readonly path: \"/v1/partner/analytics/export/ads-by-day\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n endDate: string;\n format?: \"csv\" | \"json\";\n startDate: string;\n};\n response: {\n data?: Array<{\n ad_name?: string;\n advertiser_name?: string;\n campaign_type?: string;\n completion_rate?: number;\n date?: string;\n gross_revenue_cents?: number;\n plays_completed?: number;\n publisher_revenue_cents?: number;\n publisher_share_rate?: number;\n screen_name?: string;\n }>;\n status?: boolean;\n};\n}\n\n/**\n * 24-hour distribution across the partner's fleet\n * GET /v1/partner/analytics/fleet-hourly\n */\nexport interface getFleetHourlyOperation {\n readonly path: \"/v1/partner/analytics/fleet-hourly\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n /** One entry per hour of the day. A partner with no screens gets the empty form `{ hours: [], timeRange }` instead of an array. */\n data?: Array<Record<string, unknown>> | {\n hours?: Array<Record<string, unknown>>;\n timeRange?: string;\n };\n status?: boolean;\n};\n}\n\n/**\n * 4-stage discovery → awareness → engagement → conversion funnel\n * GET /v1/partner/analytics/halo-funnel\n */\nexport interface getHaloFunnelOperation {\n readonly path: \"/v1/partner/analytics/halo-funnel\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n stages?: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * 24-hour returning-vs-first-timer audience clock\n * GET /v1/partner/analytics/loyalty-clock\n */\nexport interface getLoyaltyClockOperation {\n readonly path: \"/v1/partner/analytics/loyalty-clock\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n hours?: Array<{\n firstTimerShare?: number;\n hour?: number;\n returningShare?: number;\n sampleSize?: number;\n }>;\n sampleSize?: number;\n timeRange?: string;\n };\n status?: boolean;\n};\n}\n\n/**\n * Fleet-wide audience summary for the partner's screens\n * GET /v1/partner/analytics/network-summary\n */\nexport interface getNetworkSummaryOperation {\n readonly path: \"/v1/partner/analytics/network-summary\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n dataAvailable?: boolean;\n queriedAt?: string;\n timeRange?: string;\n totalScreens?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Daily/hourly fleet-wide audience timeseries\n * GET /v1/partner/analytics/network-trend\n */\nexport interface getNetworkTrendOperation {\n readonly path: \"/v1/partner/analytics/network-trend\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n interval?: \"1h\" | \"1d\";\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n /** One bucket per `interval`. A partner with no screens gets the empty form `{ series: [], interval, timeRange }` instead of an array. */\n data?: Array<Record<string, unknown>> | {\n interval?: string;\n series?: Array<Record<string, unknown>>;\n timeRange?: string;\n };\n status?: boolean;\n};\n}\n\n/**\n * Cross-screen proximity-graph summary\n * GET /v1/partner/analytics/proximity-summary\n */\nexport interface getProximitySummaryOperation {\n readonly path: \"/v1/partner/analytics/proximity-summary\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n continuityEdges?: Array<Record<string, unknown>>;\n crossScreenSignals?: number;\n gateDiagnostic?: Record<string, unknown>;\n repeatSignals?: number;\n sourceBreakdown?: Record<string, unknown>;\n topPaths?: Array<Record<string, unknown>>;\n uniqueSignals?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Per-creative scatter of avg-attention vs revenue-per-impression\n * GET /v1/partner/analytics/revenue-attention-correlation\n */\nexport interface getRevenueAttentionCorrelationOperation {\n readonly path: \"/v1/partner/analytics/revenue-attention-correlation\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n timeRange?: \"today\" | \"last_7d\" | \"last_30d\";\n};\n response: {\n data?: {\n points?: Array<Record<string, unknown>>;\n trendLine?: Record<string, unknown> | null;\n };\n status?: boolean;\n};\n}\n\n/**\n * Paginated chronological feed of scene observations\n * GET /v1/partner/analytics/scene-feed\n */\nexport interface getSceneFeedOperation {\n readonly path: \"/v1/partner/analytics/scene-feed\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n offset?: number;\n};\n response: {\n data?: {\n /** Distinct calendar days with observations over the last 90 days. */\n activeDays?: number;\n /** Whether another page exists after this one. */\n has_more?: boolean;\n nextOffset?: number | null;\n scenes?: Array<Record<string, unknown>>;\n /** Distinct observations within the feed window — the exact set the feed pages over. */\n total?: number;\n /** Distinct (screen, scene-prefix) observations over the last 90 days (gamification stat; independent of the page window). */\n totalObservations?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Latest scene description per screen\n * GET /v1/partner/analytics/scene-intelligence\n */\nexport interface getSceneIntelligenceOperation {\n readonly path: \"/v1/partner/analytics/scene-intelligence\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Present on EVERY response, success included. A field that appeared only on failure would make its absence the signal, and an absent field is indistinguishable from an old client or a version skew. */\n sceneDiagnostic: {\n /** True when the scene read completed and `scenes` reflects the fleet. False when it did not run to completion — `scenes` is then empty for want of an answer, not because the fleet is quiet. */\n measured: boolean;\n /** Null when `measured` is true. Otherwise names the operational failure. */\n reason: \"timeout\" | \"error\" | \"dependency_disabled\" | \"unknown\" | null | null;\n };\n scenes: Array<Record<string, unknown>>;\n };\n status?: boolean;\n};\n}\n\n/**\n * Rotate API key\n * POST /v1/partner/api-key/rotate\n */\nexport interface rotateApiKeyOperation {\n readonly path: \"/v1/partner/api-key/rotate\";\n readonly method: \"POST\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** New API key — shown only once! */\n api_key?: string;\n grace_period_hours?: number;\n message?: string;\n /** When the old key stops working */\n previous_key_expires_at?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get partner-wide audience analytics\n * GET /v1/partner/audience-analytics\n */\nexport interface getAudienceAnalyticsOperation {\n readonly path: \"/v1/partner/audience-analytics\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n period?: \"24h\" | \"7d\" | \"30d\";\n};\n response: AudienceAnalyticsResponse;\n}\n\n/**\n * Export audience data\n * GET /v1/partner/audience-export\n */\nexport interface exportAudienceDataOperation {\n readonly path: \"/v1/partner/audience-export\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n aggregation?: \"hourly\" | \"daily\" | \"weekly\";\n end_date?: string;\n format?: \"json\" | \"csv\";\n screen_ids: string;\n start_date?: string;\n};\n response: AudienceExportResponse;\n}\n\n/**\n * Get audience heatmap data\n * GET /v1/partner/audience/heatmap\n */\nexport interface getAudienceHeatmapOperation {\n readonly path: \"/v1/partner/audience/heatmap\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n days?: number;\n screen_ids?: string;\n};\n response: {\n cells?: number;\n heatmap?: Array<Record<string, unknown>>;\n lookback_days?: number;\n object?: string;\n request_id?: string;\n screens_included?: number;\n};\n}\n\n/**\n * Get live audience data for a screen\n * GET /v1/partner/audience/live/{screenId}\n */\nexport interface getLiveAudienceOperation {\n readonly path: \"/v1/partner/audience/live/{screenId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n /** Null when screen is offline */\n audience?: {\n ad_receptivity?: number | null;\n attention_score?: number;\n crowd_density?: number;\n dominant_ambience?: string | null;\n dwell_time_ms?: number | null;\n emotional_engagement?: number | null;\n face_count?: number;\n income_level?: string | null;\n lifestyle_segment?: string | null;\n mood?: string | null;\n noise_level?: number | null;\n occupancy?: string | null;\n /** The speech classifier's reading when the screen reported one (`BROWSING`, `RESEARCHING`, `COMPARING`, `READY_TO_BUY`, `POST_PURCHASE`, `COMPLAINING`, `NONE`), otherwise the segment the purchase-journey stage maps to (`high`, `medium`, `low`), otherwise null. */\n purchase_intent?: \"BROWSING\" | \"RESEARCHING\" | \"COMPARING\" | \"READY_TO_BUY\" | \"POST_PURCHASE\" | \"COMPLAINING\" | \"NONE\" | \"high\" | \"medium\" | \"low\" | null | null;\n } | null;\n last_updated?: string | null;\n /** Present when screen is offline */\n message?: string;\n object?: string;\n request_id?: string;\n screen_id?: string;\n status?: \"live\" | \"offline\";\n};\n}\n\n/**\n * Find lookalike screens\n * GET /v1/partner/audience/lookalikes/{screenId}\n */\nexport interface getLookalikeScreensOperation {\n readonly path: \"/v1/partner/audience/lookalikes/{screenId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n country?: string;\n limit?: number;\n min_similarity?: number;\n venue_type?: string;\n};\n response: {\n filters?: {\n country?: string | null;\n min_similarity?: number;\n venue_type?: string | null;\n };\n matches?: Array<Record<string, unknown>>;\n matches_count?: number;\n object?: string;\n request_id?: string;\n source_screen_id?: string;\n};\n}\n\n/**\n * Predict screen audience\n * GET /v1/partner/audience/predict/{screenId}\n */\nexport interface predictScreenAudienceOperation {\n readonly path: \"/v1/partner/audience/predict/{screenId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n day: number;\n hour: number;\n lookback_days?: number;\n};\n response: {\n day_of_week?: number;\n hour_utc?: number;\n lookback_days?: number;\n /** Present when prediction is null */\n message?: string;\n object?: string;\n /** Null if insufficient historical data */\n prediction?: Record<string, unknown> | null;\n request_id?: string;\n screen_id?: string;\n};\n}\n\n/**\n * Resolved household reach for a campaign\n * GET /v1/partner/audience/resolved-household-reach/{campaignId}\n */\nexport interface getResolvedHouseholdReachOperation {\n readonly path: \"/v1/partner/audience/resolved-household-reach/{campaignId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n campaignId: string;\n};\n query: Record<string, unknown>;\n response: {\n audience_composition?: Array<Record<string, unknown>>;\n campaign_id?: string;\n confidence_interval?: {\n lower?: number;\n upper?: number;\n };\n households_reached?: number;\n overlap_with_first_party?: number | null;\n};\n}\n\n/**\n * List audience segments\n * GET /v1/partner/audience/segments\n */\nexport interface listAudienceSegmentsOperation {\n readonly path: \"/v1/partner/audience/segments\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: Array<AudienceSegment>;\n has_more?: boolean;\n object?: string;\n request_id?: string;\n total_count?: number;\n};\n}\n\n/**\n * Create audience segment\n * POST /v1/partner/audience/segments\n */\nexport interface createAudienceSegmentOperation {\n readonly path: \"/v1/partner/audience/segments\";\n readonly method: \"POST\";\n request: {\n description?: string;\n /** Segment name */\n name: string;\n rules: Array<{\n /** Rule field (e.g., venue_type, country, city, state) */\n field: string;\n /** Comparison operator (e.g., eq, neq, in, gt, lt) */\n operator: string;\n /** Rule value */\n value: unknown;\n }>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: AudienceSegment;\n}\n\n/**\n * Delete audience segment\n * DELETE /v1/partner/audience/segments/{segmentId}\n */\nexport interface deleteAudienceSegmentOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n segmentId: string;\n};\n query: Record<string, unknown>;\n response: {\n deleted?: boolean;\n id?: string;\n object?: string;\n request_id?: string;\n};\n}\n\n/**\n * Get audience segment\n * GET /v1/partner/audience/segments/{segmentId}\n */\nexport interface getAudienceSegmentOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n segmentId: string;\n};\n query: Record<string, unknown>;\n response: AudienceSegment;\n}\n\n/**\n * Update audience segment\n * PATCH /v1/partner/audience/segments/{segmentId}\n */\nexport interface updateAudienceSegmentOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}\";\n readonly method: \"PATCH\";\n request: {\n description?: string;\n is_active?: boolean;\n name?: string;\n rules?: Array<{\n field?: string;\n operator?: string;\n value?: unknown;\n }>;\n};\n params: {\n segmentId: string;\n};\n query: Record<string, unknown>;\n response: AudienceSegment;\n}\n\n/**\n * Match screens to segment\n * POST /v1/partner/audience/segments/{segmentId}/match\n */\nexport interface matchAudienceSegmentOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}/match\";\n readonly method: \"POST\";\n request: never;\n params: {\n segmentId: string;\n};\n query: {\n limit?: number;\n own_screens_only?: \"true\" | \"false\";\n};\n response: {\n matched_count?: number;\n matches?: Array<Record<string, unknown>>;\n object?: string;\n own_screens_only?: boolean;\n request_id?: string;\n segment_id?: string;\n segment_name?: string;\n};\n}\n\n/**\n * Estimate segment reach\n * POST /v1/partner/audience/segments/{segmentId}/reach\n */\nexport interface estimateSegmentReachOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}/reach\";\n readonly method: \"POST\";\n request: never;\n params: {\n segmentId: string;\n};\n query: {\n own_screens_only?: \"true\" | \"false\";\n};\n response: {\n country_breakdown?: Record<string, number>;\n estimated_daily_cpm?: number;\n estimated_daily_impressions?: number;\n estimated_daily_revenue?: number;\n has_historical_data?: boolean;\n matched_screens?: number;\n object?: string;\n online_screens?: number;\n segment_id?: string;\n segment_name?: string;\n venue_breakdown?: Record<string, number>;\n};\n}\n\n/**\n * Generate VAST tag for segment\n * POST /v1/partner/audience/segments/{segmentId}/vast-tag\n */\nexport interface generateSegmentVastTagOperation {\n readonly path: \"/v1/partner/audience/segments/{segmentId}/vast-tag\";\n readonly method: \"POST\";\n request: {\n /** Click-through landing page URL */\n click_through?: string;\n /** Video creative URL */\n creative_url: string;\n /** Video duration in seconds */\n duration?: number;\n own_screens_only?: boolean;\n};\n params: {\n segmentId: string;\n};\n query: Record<string, unknown>;\n response: {\n creative?: {\n click_through?: string | null;\n duration?: number;\n url?: string;\n };\n cust_params?: Array<string>;\n object?: string;\n screen_ids?: Array<string>;\n segment_id?: string;\n segment_name?: string;\n targeted_screens?: number;\n vast_tag_url?: string;\n};\n}\n\n/**\n * Get audience data statistics\n * GET /v1/partner/audience/stats\n */\nexport interface getAudienceStatsOperation {\n readonly path: \"/v1/partner/audience/stats\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data_access_tier?: \"basic\" | \"developer\" | \"enterprise\";\n newest_data?: string | null;\n object?: string;\n oldest_data?: string | null;\n request_id?: string;\n screens_with_data?: number;\n total_screens?: number;\n total_vectors?: number;\n};\n}\n\n/**\n * Cancel the partner's subscription at period end\n * POST /v1/partner/billing/cancel\n */\nexport interface cancelBillingSubscriptionOperation {\n readonly path: \"/v1/partner/billing/cancel\";\n readonly method: \"POST\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n effective_at?: string;\n status?: \"canceled\" | \"scheduled_for_cancellation\";\n};\n}\n\n/**\n * Purchase committed-use credits\n * POST /v1/partner/billing/credits\n */\nexport interface purchaseCreditsOperation {\n readonly path: \"/v1/partner/billing/credits\";\n readonly method: \"POST\";\n request: {\n /** Credit purchase tier */\n tier: \"tier_500\" | \"tier_2000\" | \"tier_5000\";\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n /** Amount charged in cents */\n charged_cents?: number;\n /** Present when Stripe confirmation requires action; handle as a secret */\n client_secret?: string;\n /** Verified credit added in exact decimal cents; zero until capture settles */\n credits_added_cents?: string;\n /** Legacy field name: additional credit as a percentage of the charged amount */\n discount_pct?: number;\n object?: string;\n /** Durable purchase operation identifier */\n operation_id?: string;\n /** Stripe PaymentIntent ID */\n payment_intent_id?: string;\n request_id?: string;\n /** Complete Stripe confirmation before credit can settle */\n requires_action?: boolean;\n /** Native Stripe PaymentIntent status */\n status?: string;\n};\n}\n\n/**\n * Create Stripe Checkout session for credits\n * POST /v1/partner/billing/credits/checkout\n */\nexport interface createCreditCheckoutOperation {\n readonly path: \"/v1/partner/billing/credits/checkout\";\n readonly method: \"POST\";\n request: {\n /** URL to redirect if payment is cancelled */\n cancel_url?: string;\n /** URL to redirect after successful payment */\n success_url?: string;\n tier: \"tier_500\" | \"tier_2000\" | \"tier_5000\";\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n /** Stripe Checkout URL — redirect user here */\n checkout_url?: string;\n message?: string;\n object?: string;\n /** Durable credit-purchase operation identifier */\n operation_id?: string;\n request_id?: string;\n /** Stripe Checkout session ID */\n session_id?: string;\n};\n}\n\n/**\n * List available partner billing plans\n * GET /v1/partner/billing/plans\n */\nexport interface listBillingPlansOperation {\n readonly path: \"/v1/partner/billing/plans\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n plans?: Array<{\n included_ads?: number;\n included_impressions?: number;\n overage_per_1k_impressions_cents?: number;\n price_cents_monthly?: number;\n tier?: string;\n }>;\n};\n}\n\n/**\n * Set up usage-based billing\n * POST /v1/partner/billing/setup\n */\nexport interface setupBillingOperation {\n readonly path: \"/v1/partner/billing/setup\";\n readonly method: \"POST\";\n request: {\n /** Stripe payment method token obtained from Stripe.js or Stripe Elements */\n payment_method_id: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n billing_status?: string;\n message?: string;\n object?: string;\n request_id?: string;\n stripe_customer_id?: string;\n};\n}\n\n/**\n * Subscribe the partner to a billing plan\n * POST /v1/partner/billing/subscribe\n */\nexport interface createBillingSubscriptionOperation {\n readonly path: \"/v1/partner/billing/subscribe\";\n readonly method: \"POST\";\n request: {\n tier: \"developer\" | \"enterprise\";\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n checkout_url?: string | null;\n status?: \"active\" | \"requires_payment\";\n};\n}\n\n/**\n * Get the partner's current subscription\n * GET /v1/partner/billing/subscription\n */\nexport interface getBillingSubscriptionOperation {\n readonly path: \"/v1/partner/billing/subscription\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n current_period_end?: string;\n status?: \"active\" | \"past_due\" | \"canceled\" | \"trialing\";\n stripe_customer_id?: string | null;\n tier?: string;\n};\n}\n\n/**\n * Get the partner's current-period usage\n * GET /v1/partner/billing/usage\n */\nexport interface getBillingUsageOperation {\n readonly path: \"/v1/partner/billing/usage\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n ads_served?: number;\n audience_api_calls?: number;\n impressions?: number;\n overage_projection_cents?: number;\n period_end?: string;\n period_start?: string;\n};\n}\n\n/**\n * Get current billing period usage\n * GET /v1/partner/billing/usage-summary\n */\nexport interface getBillingUsageSummaryOperation {\n readonly path: \"/v1/partner/billing/usage-summary\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n billing_status: \"free\" | \"none\" | \"active\" | \"suspended\";\n /** Committed credit balance in cents, with eight decimal places */\n credit_balance_cents: string;\n /** Credit held by in-flight reservations, in cents with eight decimal places */\n credit_reserved_cents: string;\n object: string;\n /** Partner billing account identifier */\n partner_mongo_id: string;\n /** Exclusive end of the current UTC calendar month */\n period_end: string;\n period_start: string;\n products: Record<string, {\n meters: Record<string, {\n cost_cents: string;\n free_quantity: string;\n legacy_unknown_cents: string;\n paid_quantity: string;\n postpaid_cents: string;\n prepaid_cents: string;\n /** Total quantity in the published meter unit */\n total_quantity: string;\n }>;\n /** Product cost in cents with eight decimal places */\n total_cost_cents: string;\n }>;\n request_id: string;\n /** Period cost in cents with eight decimal places */\n total_cost_cents: string;\n};\n}\n\n/**\n * Stripe webhook callback\n * POST /v1/partner/billing/webhook\n */\nexport interface stripeWebhookCallbackOperation {\n readonly path: \"/v1/partner/billing/webhook\";\n readonly method: \"POST\";\n request: Record<string, unknown>;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: unknown;\n}\n\n/**\n * Get partner dashboard overview\n * GET /v1/partner/connect/dashboard\n */\nexport interface getConnectDashboardOperation {\n readonly path: \"/v1/partner/connect/dashboard\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n period?: \"7d\" | \"30d\" | \"90d\";\n};\n response: {\n data?: {\n charts?: {\n daily_impressions?: Array<{\n date?: string;\n earnings_cents?: number;\n impressions?: number;\n }>;\n hourly_today?: Array<{\n hour?: number;\n impressions?: number;\n }>;\n };\n earnings?: {\n available_cents?: number;\n daily_average_cents?: number;\n lifetime_cents?: number;\n pending_payout_cents?: number;\n period_cents?: number;\n today_cents?: number;\n };\n impressions?: {\n all_time?: number;\n daily_average?: number;\n period?: number;\n today?: number;\n trend_percent?: number;\n };\n partner?: {\n cpm_cents?: number;\n name?: string;\n revenue_share_percent?: number;\n slug?: string;\n status?: string;\n stripe_status?: \"pending\" | \"active\" | \"restricted\" | \"not_connected\";\n };\n period?: {\n days?: number;\n end?: string;\n start?: string;\n };\n screens?: {\n offline?: number;\n online?: number;\n pending?: number;\n total?: number;\n };\n top_screens?: Array<{\n earnings_cents?: number;\n impressions?: number;\n location?: string | null;\n name?: string;\n screen_id?: string;\n status?: \"online\" | \"offline\" | \"unknown\";\n }>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get earnings chart data\n * GET /v1/partner/connect/earnings\n */\nexport interface getConnectEarningsChartOperation {\n readonly path: \"/v1/partner/connect/earnings\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n group_by?: \"day\" | \"week\";\n period?: \"7d\" | \"30d\" | \"90d\";\n};\n response: {\n data?: {\n chart?: Array<{\n avg_view_duration_ms?: number;\n date?: string;\n earnings_cents?: number;\n impressions?: number;\n label?: string;\n unique_screens?: number;\n }>;\n period?: {\n days?: number;\n end?: string;\n start?: string;\n };\n summary?: {\n data_points?: number;\n group_by?: string;\n total_earnings_cents?: number;\n total_impressions?: number;\n };\n };\n success?: boolean;\n};\n}\n\n/**\n * Get screen health status\n * GET /v1/partner/connect/health\n */\nexport interface getConnectScreenHealthOperation {\n readonly path: \"/v1/partner/connect/health\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n status?: \"online\" | \"offline\" | \"all\";\n};\n response: {\n data?: {\n checked_at?: string;\n filter?: string;\n screens?: Array<{\n device_id?: string;\n external_device_id?: string;\n fingerprint?: string;\n last_heartbeat?: string | null;\n last_seen?: string | null;\n location?: {\n city?: string | null;\n lat?: number | null;\n lng?: number | null;\n state?: string | null;\n };\n name?: string;\n registered_at?: string;\n screen_id?: string | null;\n screen_status?: \"pending\" | \"active\" | \"inactive\";\n status?: \"online\" | \"offline\" | \"never_connected\" | \"unknown\";\n time_since_last_seen_seconds?: number | null;\n uptime_24h_percent?: number | null;\n venue_type?: string | null;\n }>;\n summary?: {\n never_connected?: number;\n offline?: number;\n online?: number;\n total?: number;\n };\n };\n success?: boolean;\n};\n}\n\n/**\n * List content presets\n * GET /v1/partner/content-presets\n */\nexport interface listContentPresetsOperation {\n readonly path: \"/v1/partner/content-presets\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: Array<ContentPreset>;\n has_more?: boolean;\n object?: string;\n total_count?: number;\n url?: string;\n};\n}\n\n/**\n * Get content preset\n * GET /v1/partner/content-presets/{presetId}\n */\nexport interface getContentPresetOperation {\n readonly path: \"/v1/partner/content-presets/{presetId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n presetId: string;\n};\n query: Record<string, unknown>;\n response: ContentPreset;\n}\n\n/**\n * Create an experiment on a funded native media-buy placement\n * POST /v1/partner/creative-experiments\n */\nexport interface createCreativeFeedbackExperimentOperation {\n readonly path: \"/v1/partner/creative-experiments\";\n readonly method: \"POST\";\n request: FeedbackCreateRequest;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: FeedbackExperimentResponse;\n}\n\n/**\n * Read your native experiment, policy and latest 500 owned creative recovery records\n * GET /v1/partner/creative-experiments/{experiment_id}\n */\nexport interface getCreativeFeedbackExperimentOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackExperimentDetailResponse;\n}\n\n/**\n * Configure your native brand profile and enable its booked reactive campaign\n * POST /v1/partner/creative-experiments/{experiment_id}/brand\n */\nexport interface configureCreativeFeedbackBrandOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/brand\";\n readonly method: \"POST\";\n request: FeedbackBrandRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackBrandResponse;\n}\n\n/**\n * Generate a native brand creative for explicit review and moderation\n * POST /v1/partner/creative-experiments/{experiment_id}/creatives/generate\n */\nexport interface generateCreativeFeedbackCreativeOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/creatives/generate\";\n readonly method: \"POST\";\n request: FeedbackGenerateRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackGenerateResponse;\n}\n\n/**\n * Import an AI model’s image through native bytes, moderation and brand review\n * POST /v1/partner/creative-experiments/{experiment_id}/creatives/import\n */\nexport interface importCreativeFeedbackCreativeOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/creatives/import\";\n readonly method: \"POST\";\n request: FeedbackImportRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackGenerateResponse;\n}\n\n/**\n * Approve or reject your generated creative using native brand review\n * POST /v1/partner/creative-experiments/{experiment_id}/creatives/review\n */\nexport interface reviewCreativeFeedbackCreativeOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/creatives/review\";\n readonly method: \"POST\";\n request: FeedbackReviewRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackReviewResponse;\n}\n\n/**\n * Replace native creative weights at the expected policy version\n * PUT /v1/partner/creative-experiments/{experiment_id}/policy\n */\nexport interface updateCreativeFeedbackPolicyOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/policy\";\n readonly method: \"PUT\";\n request: FeedbackPolicyRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackExperimentResponse;\n}\n\n/**\n * Read a metered page of latest measured native play states\n * GET /v1/partner/creative-experiments/{experiment_id}/rewards\n */\nexport interface getCreativeFeedbackRewardsOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/rewards\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: {\n /** Opaque next_cursor from this same date scope; rewind after scan_complete. */\n cursor?: string;\n /** Inclusive first play date, YYYY-MM-DD; date range is at most 31 days. */\n from: string;\n /** Unique caller-owned billing key for this page; committed retries return the receipt, not another read. */\n idempotency_key: string;\n /** Maximum native play states returned on this metered page. */\n limit?: number;\n /** Inclusive last play date, YYYY-MM-DD; date range is at most 31 days. */\n to: string;\n};\n response: FeedbackRewardsResponse;\n}\n\n/**\n * Stop the native experiment at the expected policy version\n * POST /v1/partner/creative-experiments/{experiment_id}/stop\n */\nexport interface stopCreativeFeedbackExperimentOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/stop\";\n readonly method: \"POST\";\n request: FeedbackStopRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackExperimentResponse;\n}\n\n/**\n * Register approved native generated creative variants\n * POST /v1/partner/creative-experiments/{experiment_id}/variants\n */\nexport interface registerCreativeFeedbackVariantsOperation {\n readonly path: \"/v1/partner/creative-experiments/{experiment_id}/variants\";\n readonly method: \"POST\";\n request: FeedbackRegisterVariantsRequest;\n params: {\n /** Native experiment ID returned by create_creative_experiment. */\n experiment_id: string;\n};\n query: Record<string, unknown>;\n response: FeedbackExperimentResponse;\n}\n\n/**\n * Submit a creative for classification\n * POST /v1/partner/creatives\n */\nexport interface submitPartnerCreativeOperation {\n readonly path: \"/v1/partner/creatives\";\n readonly method: \"POST\";\n request: {\n /** Optional advertiser/brand label to attach. */\n advertiser_name?: string;\n /** Public http(s) URL of the creative image or video. Must resolve to a public address — private/internal/cloud-metadata addresses are rejected. */\n media_url: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: CreativeClassification;\n}\n\n/**\n * Get a creative classification\n * GET /v1/partner/creatives/{ref}\n */\nexport interface getPartnerCreativeOperation {\n readonly path: \"/v1/partner/creatives/{ref}\";\n readonly method: \"GET\";\n request: never;\n params: {\n ref: string;\n};\n query: Record<string, unknown>;\n response: CreativeClassification;\n}\n\n/**\n * Get dashboard overview\n * GET /v1/partner/dashboard\n */\nexport interface getDashboardOperation {\n readonly path: \"/v1/partner/dashboard\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: DashboardResponse;\n success?: boolean;\n};\n}\n\n/**\n * Export the cross-screen proximity graph edge list\n * GET /v1/partner/data-export/proximity-graph-edges\n */\nexport interface exportProximityGraphEdgesOperation {\n readonly path: \"/v1/partner/data-export/proximity-graph-edges\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n cursor?: string;\n days?: number;\n limit?: number;\n};\n response: {\n data?: {\n limit: number;\n lookback_days: number;\n /** The `cursor` of the next page, or `null` on the last page. */\n next_cursor: string | null;\n rows: Array<{\n destination_screen_id: string;\n /** Distinct transitions from the source screen to the destination screen. */\n edge_count: number;\n /** Median minutes between the source and destination sightings. */\n lag_p50: number;\n source_screen_id: string;\n /** Distinct devices behind those transitions. */\n unique_devices: number;\n }>;\n /** Number of edges in this page. */\n total: number;\n /** `true` when more edges follow this page: pass `next_cursor` back as `cursor`. */\n truncated: boolean;\n /** The UTC second the window ends at (`YYYY-MM-DD HH:MM:SS`), pinned by the first page. */\n window_end: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * List available datasets and per-dataset record counts\n * GET /v1/partner/data-export/summary\n */\nexport interface getDataExportSummaryOperation {\n readonly path: \"/v1/partner/data-export/summary\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n days?: number;\n};\n response: DataExportSummaryResponse;\n}\n\n/**\n * Export a partner-scoped dataset\n * GET /v1/partner/data-export/{dataset}\n */\nexport interface exportDatasetOperation {\n readonly path: \"/v1/partner/data-export/{dataset}\";\n readonly method: \"GET\";\n request: never;\n params: {\n dataset: \"audience_metrics\" | \"signal_audience\" | \"attention_ledger\" | \"impression_ledger\" | \"waterfall_events\" | \"intent_signals\" | \"audience_loyalty_features\" | \"screen_metadata\";\n};\n query: {\n days?: number;\n limit?: number;\n offset?: number;\n};\n response: {\n data?: {\n lookback_days?: number;\n /** Row shape depends on the requested `dataset`. For `audience_loyalty_features` see `AudienceLoyaltyFeaturesRow`. For other datasets the row shape is documented per-dataset in the Trillboards data dictionary. */\n rows?: Array<AudienceLoyaltyFeaturesRow | Record<string, unknown>>;\n total?: number;\n truncated?: boolean;\n };\n success?: boolean;\n};\n}\n\n/**\n * Geographic audience panel (data-licensing)\n * GET /v1/partner/data/geohash-audience-panel\n */\nexport interface getGeohashAudiencePanelOperation {\n readonly path: \"/v1/partner/data/geohash-audience-panel\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n date?: string;\n geohash_prefix?: string;\n limit?: number;\n offset?: number;\n venue_category?: string;\n};\n response: {\n data?: {\n /** The build of the day this page read (UTC, `YYYY-MM-DD HH:MM:SS.mmm`). A day with no build is answered `404` `day_not_built`. */\n built_at: string;\n /** Day exported (UTC). */\n date: string;\n /** The panel's definition (2 since 2026-10-19). */\n definition_version: number;\n /** Rows per page this response was read with. */\n limit: number;\n /** The `offset` of the next page, or `null` on the last page. */\n next_offset: number | null;\n /** Rows skipped before this page. */\n offset: number;\n /** Integer partner_id (`partners.partner_id`). */\n partner_id: number;\n /** One row per (hour, geohash6, venue_category), every cell with its counts. */\n rows: Array<{\n /** Mean device head-pose index 0-1 over the camera windows. `null` when the cell-hour holds no camera window. */\n avg_attention: number | null;\n /** Mean dwell per window in milliseconds, each window's dwell capped at its own length, over the windows that carry a start time (dwell_capped_windows). `null`: No window of the row carries a start time (dwell_capped_windows = 0). */\n avg_dwell_ms: number | null;\n /** Mean capped dwell per camera window, seconds (`avg_dwell_ms` / 1000). `null` when no camera window of the cell-hour carries a start time. */\n avg_dwell_seconds: number | null;\n /** Mean faces per camera window. `null` when the cell-hour holds no camera window. */\n avg_face_count: number | null;\n /** The windows avg_dwell_ms averages: camera windows that carry a start time. */\n dwell_capped_windows: number;\n /** Of those, the face-backed windows. */\n face_backed_windows: number;\n /** Of those, the identifiers whose strongest signal was below it: the devices around the screen. Sources that report no signal strength (mDNS, SSDP, HTTP probe) are in neither split. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n far_key_slots: number | null;\n /** far_key_slots / radio_slots. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n far_keys_per_slot: number | null;\n /** Median age of those fixes at the window's instant. `null`: No camera window of the row was placed by a trail fix. */\n fix_age_p50_s: number | null;\n /** 90th-percentile age of those fixes. `null`: No camera window of the row was placed by a trail fix. */\n fix_age_p90_s: number | null;\n /** Camera windows of a fixed screen with no trail fix in the lookback, placed at its registered site. */\n fixed_site_positioned_windows: number;\n /** Geohash-6 cell (about 1.2 x 0.6 km) where the screen was at the unit's instant: on its map-matched path, else at its last GPS trail fix, else (a fixed screen) at its registered site. */\n geohash6: string;\n /** Start of the UTC hour, `YYYY-MM-DD HH:MM:SS`. A camera window counts in the hour it closed, a radio slot in the hour of its midpoint. */\n hour: string;\n /** `null` on every row: no income source joins the panel (definition 1 wrote `unknown`). */\n income_tier: string | null;\n /** Camera windows placed at the screen's last GPS fix at or before their instant. */\n last_fix_positioned_windows: number;\n /** The near-field boundary of the row's day: the active pass_audience method register's rf_near_rssi_dbm, the boundary the in-cab radio uses. */\n near_field_rssi_dbm: number;\n /** Of those, the identifiers whose strongest signal in the slot was at or above near_field_rssi_dbm: in the vehicle or against the screen. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n near_key_slots: number | null;\n /** near_key_slots / radio_slots: near-field identifiers concurrent in one slot. The approved metric radio_identifiers_per_person converts near-field passenger Bluetooth identifiers to people: it is measured in the cab, where the driver's phone and the vehicle's own devices are removed by role. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n near_keys_per_slot: number | null;\n /** Radio observations (pings) of those identifiers in the cell-hour (`radio_pings`). `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n observation_count: number | null;\n /** Camera-graded windows (face- or person-backed, ~10 s each) placed in the cell. */\n observation_windows: number;\n /** Exact 95th percentile of the per-window face reading over the row's windows. `null`: No camera window in the row. */\n p95_face_count: number | null;\n /** Camera windows placed on the map-matched vehicle path at their instant. */\n path_positioned_windows: number;\n /** Of those, the person-backed windows. */\n person_backed_windows: number;\n /** The windows persons_per_window averages. */\n person_reading_windows: number;\n /** Mean people in view per camera window, faced or not: per window the larger of its mean person count and its mean face count (the in-cab panel's persons reading), over the windows that carry one. `null`: No camera window of the row carries a persons reading (person_reading_windows = 0). */\n persons_per_window: number | null;\n /** Radio slots of a fixed screen with no trail fix in the lookback, placed at its registered site. */\n radio_fixed_site_positioned_slots: number;\n /** Radio slots placed at the screen's last GPS fix at or before their midpoint. */\n radio_last_fix_positioned_slots: number;\n /** Radio slots placed on the map-matched path at their midpoint. */\n radio_path_positioned_slots: number;\n /** Radio observations of the visitor identifiers in the row's slots. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n radio_pings: number | null;\n /** Distinct screens whose radio slots the row holds. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n radio_screens: number | null;\n /** 6-minute radio slots of screens placed in the cell: a slot in which the screen's visitor radio read an identifier (a slot in which the scanner reported only the screen's own Wi-Fi access point does not show the visitor radio ran, and is no slot here). 0 when only camera windows fell in the row. */\n radio_slots: number;\n /** Distinct screens whose radio slots the cell-hour holds (`radio_screens`). `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n screen_count: number | null;\n /** Distinct screens whose camera windows the row holds. */\n screens: number;\n /** Distinct visitor-source radio identifiers (BLE, mDNS, SSDP, HTTP probe) over the cell-hour's radio slots, exact. A distinct count: rows do not add. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n unique_devices: number | null;\n /** The screen's registered venue category (`transit` for a vehicle screen); empty when the registry holds none. */\n venue_category: string;\n /** Visitor-source radio identifiers (BLE, mDNS, SSDP, HTTP probe) distinct within each slot, summed over the slots. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n visitor_key_slots: number | null;\n /** visitor_key_slots / radio_slots: identifiers concurrent in one 6-minute slot, every visitor source and both fields. `null`: No radio slot in the row (radio_slots = 0): only camera windows were placed there, and the radio was not read. */\n visitor_keys_per_slot: number | null;\n }>;\n /** `true` when more cells follow this page: request `next_offset`. */\n truncated: boolean;\n };\n success?: boolean;\n};\n}\n\n/**\n * Outcome-attribution export (data-licensing)\n * GET /v1/partner/data/outcome-attribution\n */\nexport interface getOutcomeAttributionOperation {\n readonly path: \"/v1/partner/data/outcome-attribution\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n creative_id?: string;\n cursor?: string;\n days?: number;\n grain?: \"person\" | \"device\";\n limit?: number;\n};\n response: {\n data?: {\n /** The reach grain applied (echoes the `grain` query param; defaults to `person`). */\n grain: \"person\" | \"device\";\n /** Rows per page this response was read with. */\n limit: number;\n /** The validated lookback window used. */\n lookback_days: number;\n /** The `cursor` of the next page, or `null` on the last page. */\n next_cursor: string | null;\n /** Per-(creative, screen) outcome aggregates, every cell with its counts. */\n rows: Array<{\n /** Average exposures per reach-identity within the (creative, screen) cell (count of detail rows / distinct reach keys). A frequency over PEOPLE under person grain, over devices under device grain. */\n avg_frequency: number;\n /** Creative identifier from `completed_impressions.creative_id`. */\n creative_id: string;\n /** Count of exposed identities also seen on another screen. */\n cross_screen_devices: number;\n /** Count of distinct devices exposed to the creative on the screen. */\n exposed_devices: number;\n /** returned_devices / exposed_devices. */\n return_rate: number;\n /** Count of exposed devices that returned (≤ exposed_devices). */\n returned_devices: number;\n /** The partner's own screen identifier. */\n screen_mongo_id: string;\n }>;\n /** Number of rows in this page. */\n total: number;\n /** `true` when more cells follow this page: pass `next_cursor` back as `cursor`. */\n truncated: boolean;\n /** The UTC date the window ends on, pinned by the first page; the window is `window_end - lookback_days` through `window_end`. */\n window_end: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Register a device\n * POST /v1/partner/device\n */\nexport interface registerDeviceOperation {\n readonly path: \"/v1/partner/device\";\n readonly method: \"POST\";\n request: {\n /** Any additional metadata for your records */\n custom_metadata?: Record<string, unknown>;\n /** Screen or venue description for enrichment */\n description?: string;\n /** Your internal device identifier */\n device_id: string;\n /** Mutable partner category; defaults to other only on a new registration. Omit it to preserve an existing device's category. */\n device_type?: \"tv\" | \"tablet\" | \"display\" | \"kiosk\" | \"other\" | \"mobile\" | \"connected_tv\" | \"digital_signage\" | \"vending_machine\";\n /** Screen display specification. `width`/`height` are pixel resolution; `width_in`/`height_in` are physical inches. For accurate floor pricing we need physical inches — pass `width_in` + `height_in` if you've measured the screen, OR `ppi` so the server can derive inches from the pixel resolution. Pixel-only inputs (no `ppi`, no `*_in`) will NOT be recorded as physical size; they're still used for orientation detection and DSP slot dimensions. */\n display?: {\n /** Display resolution height in pixels (e.g. 1080) */\n height?: number;\n /** Physical screen height in INCHES. Pairs with `width_in`. */\n height_in?: number;\n orientation?: \"landscape\" | \"portrait\";\n /** Pixels per inch. Required if you want physical screen size derived from `width`/`height` (server computes widthInches = width / ppi). Typical: 40 (large TV), 96 (desktop monitor), 264 (tablet). */\n ppi?: number;\n /** Display resolution width in pixels (e.g. 1920) */\n width?: number;\n /** Physical screen width in INCHES. Overrides ppi-based derivation. Use this when you've measured the actual screen (most accurate for pricing). */\n width_in?: number;\n };\n display_settings?: PartnerRegistrationDisplaySettings;\n location?: {\n address?: string;\n city?: string;\n country?: string;\n lat?: number;\n lng?: number;\n state?: string;\n timezone?: string;\n venue_name?: string;\n venue_type?: \"mall\" | \"airport\" | \"retail\" | \"restaurant\" | \"office\" | \"gym\" | \"hospital\" | \"school\" | \"other\";\n zip?: string;\n };\n /** Reporting hints for moving screens (mobility_type mobile/semi_mobile). */\n mobile_settings?: {\n /** Minimum displacement before a fix is sent (device-side hint). */\n distance_threshold_meters?: number;\n /** Target GPS reporting interval (floor — the platform adapts to speed/battery). */\n update_interval_seconds?: number;\n vehicle_type?: \"taxi\" | \"rideshare\" | \"delivery\" | \"bus\" | \"food_truck\" | \"other\";\n };\n /** Declare a moving screen. `mobile` (taxi-top, transit) and `semi_mobile` (food truck, pop-up) screens report GPS via the heartbeat `location` field; `static` (default) screens ignore it. */\n mobility_type?: \"static\" | \"mobile\" | \"semi_mobile\";\n /** Friendly name for the device */\n name?: string;\n /** Photo URL for enrichment context */\n photo_url?: string;\n /** Optional additional photos for enrichment */\n photos?: Array<{\n /** photo type (context, close, angle, other) */\n type?: string;\n url?: string;\n }>;\n /** Physical placement details (optional) */\n placement?: {\n height_from_floor_ft?: number;\n mounting_type?: \"wall\" | \"ceiling\" | \"tabletop\" | \"window\" | \"stand\" | \"freestanding\" | \"floor_stand\" | \"counter\" | \"pole\" | \"vehicle\" | \"other\" | \"unknown\";\n orientation?: \"landscape\" | \"portrait\";\n viewing_distance_ft?: number;\n };\n /** Venue classification details (optional). Subcategory is free text and may be auto-enriched by AI. */\n venue?: {\n business_name?: string;\n category?: \"retail\" | \"restaurant\" | \"bar\" | \"cafe\" | \"hotel\" | \"office\" | \"gym\" | \"hospital\" | \"school\" | \"mall\" | \"airport\" | \"gas_station\" | \"convenience_store\" | \"laundromat\" | \"salon\" | \"auto_shop\" | \"other\";\n context_notes?: string;\n environment_type?: \"indoor\" | \"outdoor\" | \"semi_outdoor\" | \"transit\";\n placement_type?: \"digital_signage\" | \"point_of_sale\" | \"waiting_area\" | \"window_display\" | \"kiosk\" | \"other\";\n /** Free text subcategory (may be auto-enriched by AI) */\n subcategory?: string;\n };\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Our internal device ID */\n device_id?: string;\n /** Saved partner category, independent of advertising classification. */\n device_type?: \"tv\" | \"tablet\" | \"display\" | \"kiosk\" | \"other\" | \"mobile\" | \"connected_tv\" | \"digital_signage\" | \"vending_machine\";\n /** Load this URL in your WebView */\n embed_url?: string;\n /** Enrichment completion state */\n enrichment_status?: string;\n /** Your device ID */\n external_device_id?: string;\n /** Device fingerprint for SDK */\n fingerprint?: string;\n location_capability?: PartnerLocationCapability;\n /** Stripe-style readiness. `ready` — the screen has its own coordinates and its venue is classified. `limited` — servable, but not on every rail: the venue is not yet classified, or the screen has no position of its own and is listed in inventory feeds at your fleet's market location. `blocked` — no rail has a location, or classification permanently failed. A device is never refused registration for missing location. */\n monetization_status?: \"ready\" | \"limited\" | \"blocked\";\n requirements?: PartnerMonetizationRequirements;\n /** Linked screen ID */\n screen_id?: string;\n status?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Delete a device\n * DELETE /v1/partner/device/{deviceId}\n */\nexport interface deleteDeviceOperation {\n readonly path: \"/v1/partner/device/{deviceId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get device info\n * GET /v1/partner/device/{deviceId}\n */\nexport interface getDeviceOperation {\n readonly path: \"/v1/partner/device/{deviceId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: Device;\n success?: boolean;\n};\n}\n\n/**\n * Update device metadata\n * PATCH /v1/partner/device/{deviceId}\n */\nexport interface updateDeviceOperation {\n readonly path: \"/v1/partner/device/{deviceId}\";\n readonly method: \"PATCH\";\n request: {\n /** Any additional metadata for your records */\n custom_metadata?: Record<string, unknown>;\n /** Screen or venue description */\n description?: string;\n /** Mutable partner category. Omit it to preserve the current value; changing it does not alter advertising classification. */\n device_type?: \"tv\" | \"tablet\" | \"display\" | \"kiosk\" | \"other\" | \"mobile\" | \"connected_tv\" | \"digital_signage\" | \"vending_machine\";\n /** Screen display specification. `width`/`height` are pixels; pass `width_in`/`height_in` (inches) or `ppi` to record physical screen size for floor pricing. See POST /device for full unit semantics. */\n display?: {\n /** Display resolution height in pixels */\n height?: number;\n /** Physical screen height in inches (pairs with width_in) */\n height_in?: number;\n orientation?: \"landscape\" | \"portrait\";\n /** Pixels per inch (required to derive physical size from width/height) */\n ppi?: number;\n /** Display resolution width in pixels */\n width?: number;\n /** Physical screen width in inches (overrides ppi-based derivation) */\n width_in?: number;\n };\n /** Partner-owned device ID used for idempotent lookup and reconciliation */\n external_device_id?: string;\n location?: {\n address?: string;\n city?: string;\n country?: string;\n lat?: number;\n lng?: number;\n state?: string;\n timezone?: string;\n /** Alias for `venue.category` — a granular venue type. Accepted on PATCH (previously documented on register only). When both are sent, `venue.category` wins. On the response this field returns the OpenOOH CHILD slug (e.g. \"hotels\"/\"malls\"); `venue.category` returns the parent. */\n venue_type?: \"mall\" | \"airport\" | \"retail\" | \"restaurant\" | \"office\" | \"gym\" | \"hospital\" | \"school\" | \"other\";\n zip?: string;\n };\n /** Reporting hints for moving screens. Merges with existing settings. */\n mobile_settings?: {\n distance_threshold_meters?: number;\n update_interval_seconds?: number;\n vehicle_type?: \"taxi\" | \"rideshare\" | \"delivery\" | \"bus\" | \"food_truck\" | \"other\";\n };\n /** Update the screen's mobility. Set `mobile`/`semi_mobile` to have the heartbeat `location` field accepted for a moving screen. */\n mobility_type?: \"static\" | \"mobile\" | \"semi_mobile\";\n /** Friendly name for the device */\n name?: string;\n photo_url?: string;\n photos?: Array<{\n type?: string;\n url?: string;\n }>;\n placement?: {\n height_from_floor_ft?: number;\n mounting_type?: \"wall\" | \"ceiling\" | \"tabletop\" | \"window\" | \"stand\" | \"freestanding\" | \"floor_stand\" | \"counter\" | \"pole\" | \"vehicle\" | \"other\" | \"unknown\";\n orientation?: \"landscape\" | \"portrait\";\n viewing_distance_ft?: number;\n };\n venue?: {\n business_name?: string;\n /** Granular venue type on WRITE (child-level slug). On READ this returns the resolved OpenOOH PARENT (e.g. \"hotel\" write -> \"entertainment\" read); the child slug rides `venue.subcategory` and the numeric child id rides `venue.openooh_venue_type_id`. */\n category?: \"retail\" | \"restaurant\" | \"bar\" | \"cafe\" | \"hotel\" | \"office\" | \"gym\" | \"hospital\" | \"school\" | \"mall\" | \"airport\" | \"gas_station\" | \"convenience_store\" | \"laundromat\" | \"salon\" | \"auto_shop\" | \"other\";\n context_notes?: string;\n environment_type?: \"indoor\" | \"outdoor\" | \"semi_outdoor\" | \"transit\";\n /** Response-only. Canonical OpenOOH child id to reconcile the declared venue type against (e.g. 807 for hotels). */\n openooh_venue_type_id?: number;\n placement_type?: \"digital_signage\" | \"point_of_sale\" | \"waiting_area\" | \"window_display\" | \"kiosk\" | \"other\";\n /** OpenOOH child slug (e.g. \"hotels\"). Free text on write; resolved on read. */\n subcategory?: string;\n };\n};\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: Device;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get ads for device\n * GET /v1/partner/device/{deviceId}/ads\n */\nexport interface getAdsForDeviceOperation {\n readonly path: \"/v1/partner/device/{deviceId}/ads\";\n readonly method: \"GET\";\n request: never;\n params: {\n deviceId: string;\n};\n query: {\n autoplay?: boolean;\n ipd?: string;\n lang?: string;\n muted?: boolean;\n orientation?: \"portrait\" | \"landscape\";\n slot_h?: number;\n slot_w?: number;\n test?: \"1\" | \"true\";\n ua?: string;\n};\n response: {\n data?: {\n ads?: Array<Ad>;\n cache_until?: string;\n /** Pending device commands to execute (remote management). */\n commands?: Array<{\n command?: string;\n id?: string;\n payload?: Record<string, unknown>;\n queued_at?: string;\n }>;\n /** Venue/content keywords for contextual targeting. Omitted from the response when empty. */\n content_keywords?: Array<string>;\n device?: {\n display?: {\n height?: number | null;\n width?: number | null;\n };\n fingerprint?: string;\n };\n /** DOOH context signals (venue type, geo, audience) forwarded to demand. */\n dooh_signals?: Record<string, unknown>;\n header_bidding_settings?: HeaderBiddingSettings;\n /** URL of the Trillboards-hosted IMA renderer for this screen, or null when the IMA player is not enabled for this deployment / there is no waterfall source. */\n ima_player_url?: string | null;\n screen_dimensions?: {\n height?: number;\n orientation?: \"portrait\" | \"landscape\";\n width?: number;\n };\n screen_id?: string;\n /** Serve locale derived from the venue country (e.g. \"en\", \"fr\"). */\n screen_locale?: string;\n screen_name?: string | null;\n screen_orientation?: \"portrait\" | \"landscape\";\n settings?: AdSettings;\n };\n success?: boolean;\n};\n}\n\n/**\n * Acknowledge a device command\n * POST /v1/partner/device/{deviceId}/commands/{commandId}/ack\n */\nexport interface ackDeviceCommandOperation {\n readonly path: \"/v1/partner/device/{deviceId}/commands/{commandId}/ack\";\n readonly method: \"POST\";\n request: {\n /** Command execution result */\n result?: Record<string, unknown>;\n /** Acknowledgment status */\n status?: string;\n};\n params: {\n commandId: string;\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Read live device heartbeat state\n * GET /v1/partner/device/{deviceId}/heartbeat\n */\nexport interface getDeviceHeartbeatOperation {\n readonly path: \"/v1/partner/device/{deviceId}/heartbeat\";\n readonly method: \"GET\";\n request: never;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: PartnerHeartbeatQueryResponse;\n}\n\n/**\n * Device heartbeat\n * POST /v1/partner/device/{deviceId}/heartbeat\n */\nexport interface heartbeatOperation {\n readonly path: \"/v1/partner/device/{deviceId}/heartbeat\";\n readonly method: \"POST\";\n request: PartnerHeartbeatRequest | PartnerDeviceHeartbeatBody;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: PartnerHeartbeatResponse;\n}\n\n/**\n * Update a device's publisher impression multiplier\n * PATCH /v1/partner/device/{deviceId}/impression-multiplier\n */\nexport interface updateDeviceImpressionMultiplierOperation {\n readonly path: \"/v1/partner/device/{deviceId}/impression-multiplier\";\n readonly method: \"PATCH\";\n request: {\n /** Publisher-declared audience impressions per ad play; stored to four decimal places */\n impression_multiplier: number;\n};\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n device_id?: string;\n external_device_id?: string;\n impression_multiplier?: number;\n screen_id?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Report a rider interaction from the partner SDK (reportInteraction)\n * POST /v1/partner/device/{deviceId}/interactions\n */\nexport interface recordPartnerDeviceInteractionOperation {\n readonly path: \"/v1/partner/device/{deviceId}/interactions\";\n readonly method: \"POST\";\n request: PartnerInteraction;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data: {\n interaction_id: string;\n /** The screen this device is bound to. */\n screen_id: string;\n status: \"accepted\" | \"duplicate\";\n type: \"tap\" | \"qr_shown\" | \"qr_scanned\" | \"dwell_prompt\" | \"seatbelt_reminder_shown\";\n };\n success: true;\n};\n}\n\n/**\n * Report a play from the partner SDK (reportPlay)\n * POST /v1/partner/device/{deviceId}/plays\n */\nexport interface recordPartnerDevicePlayOperation {\n readonly path: \"/v1/partner/device/{deviceId}/plays\";\n readonly method: \"POST\";\n request: PartnerDevicePlay;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data: {\n content_sha256: string;\n play_id: string;\n /** The screen this device is bound to. */\n screen_id: string;\n status: \"accepted\" | \"duplicate\";\n };\n success: true;\n};\n}\n\n/**\n * Record programmatic ad event (Server-side Analytics)\n * POST /v1/partner/device/{deviceId}/programmatic-event\n */\nexport interface recordProgrammaticEventOperation {\n readonly path: \"/v1/partner/device/{deviceId}/programmatic-event\";\n readonly method: \"POST\";\n request: {\n ad_id?: string;\n error_code?: string;\n error_message?: string;\n event: \"ad_started\" | \"ad_ended\" | \"no_fill\" | \"error\";\n reason?: string;\n request_id?: string;\n screen_orientation?: \"portrait\" | \"landscape\";\n slot_height?: number;\n slot_width?: number;\n variant_name?: string;\n};\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Report a taxi-meter state change from the partner SDK (setTripState)\n * POST /v1/partner/device/{deviceId}/trips\n */\nexport interface recordPartnerDeviceTripStateOperation {\n readonly path: \"/v1/partner/device/{deviceId}/trips\";\n readonly method: \"POST\";\n request: PartnerTripState;\n params: {\n deviceId: string;\n};\n query: Record<string, unknown>;\n response: {\n data: {\n event_seq: number;\n /** The screen this device is bound to. */\n screen_id: string;\n status: \"accepted\" | \"duplicate\";\n trip_id: string;\n };\n success: true;\n};\n}\n\n/**\n * Get fresh VAST for a registered device\n * GET /v1/partner/device/{deviceId}/vast\n */\nexport interface getVastForDeviceOperation {\n readonly path: \"/v1/partner/device/{deviceId}/vast\";\n readonly method: \"GET\";\n request: never;\n params: {\n deviceId: string;\n};\n query: {\n autoplay?: boolean;\n connection_type?: string;\n content_id?: string;\n device_memory?: number;\n dpr?: number;\n ipd?: string;\n lang?: string;\n make?: string;\n model?: string;\n muted?: boolean;\n orientation?: \"portrait\" | \"landscape\";\n os?: string;\n platform_hint?: string;\n screen_h?: number;\n screen_w?: number;\n sdk_type?: string;\n sdk_version?: string;\n slot_h?: number;\n slot_w?: number;\n stream_type?: string;\n test?: \"1\" | \"true\";\n ua?: string;\n vp_h?: number;\n vp_w?: number;\n};\n response: unknown;\n}\n\n/**\n * List all devices\n * GET /v1/partner/devices\n */\nexport interface listDevicesOperation {\n readonly path: \"/v1/partner/devices\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n offset?: number;\n status?: \"pending\" | \"active\" | \"offline\" | \"suspended\";\n};\n response: {\n data?: {\n devices?: Array<DeviceSummary>;\n limit?: number;\n offset?: number;\n total?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Register a device (V2 single-call onboarding)\n * POST /v1/partner/devices\n */\nexport interface registerDeviceV2Operation {\n readonly path: \"/v1/partner/devices\";\n readonly method: \"POST\";\n request: {\n /** Optional display dimensions. One of three modes — the `mode` discriminator is required. */\n display?: {\n /** Physical screen height in INCHES. Pairs with `width_in`. */\n height_in: number;\n mode: \"inches\";\n /** Physical screen width in INCHES. No upper bound — large-format / billboard inventory is valid. */\n width_in: number;\n } | {\n height: number;\n mode: \"pixels_with_ppi\";\n ppi: number;\n width: number;\n } | {\n height: number;\n mode: \"pixels_only\";\n width: number;\n };\n /** Your stable per-device identifier (e.g. \"REC00005\"). */\n external_id: string;\n /** Optional hardware identity — improves DSP device matching. */\n hardware?: {\n make?: string;\n model?: string;\n os?: \"Android\" | \"iOS\" | \"Tizen\" | \"WebOS\" | \"Linux\" | \"FireOS\" | \"Other\";\n osv?: string;\n };\n /** Optional venue location — either lat/lng coordinates or a street address. */\n location?: {\n lat: number;\n lng: number;\n } | {\n address: string;\n };\n /** The device's real WebView User-Agent. Must include a Build fingerprint and be unique per device. */\n ua: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n device_id?: string;\n diagnostics_url?: string;\n /** Paste into an iframe to start serving. */\n embed_url?: string;\n /** Server-derived canonical device identity. */\n identity?: {\n derivation_confidence?: number | null;\n derivation_source?: string | null;\n device_class?: string | null;\n make?: string | null;\n model?: string | null;\n os?: string | null;\n osv?: string | null;\n };\n };\n success?: boolean;\n};\n}\n\n/**\n * Bulk register devices\n * POST /v1/partner/devices/batch\n */\nexport interface registerDevicesBatchOperation {\n readonly path: \"/v1/partner/devices/batch\";\n readonly method: \"POST\";\n request: {\n /** Default fields applied to each device */\n defaults?: {\n display_settings?: PartnerRegistrationDisplaySettings;\n };\n devices: Array<{\n device_id: string;\n device_type?: string;\n display?: Record<string, unknown>;\n display_settings?: PartnerRegistrationDisplaySettings;\n location?: Record<string, unknown>;\n name?: string;\n placement?: Record<string, unknown>;\n venue?: Record<string, unknown>;\n }>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n devices?: Array<{\n device_id?: string;\n fingerprint?: string;\n location_capability?: PartnerLocationCapability;\n monetization_status?: \"ready\" | \"limited\" | \"blocked\";\n requirements?: PartnerMonetizationRequirements;\n screen_id?: string;\n status?: string;\n trillboard_id?: string;\n }>;\n errors?: Array<{\n device_id?: string;\n error?: string;\n /** Stable code, e.g. DEVICE_ID_CLAIMED (409 — a device that already reports to Trillboards under this id is registered to another account), REGISTRATION_CHECK_UNAVAILABLE (503 — retry), INVALID_COUNTRY (400). */\n error_code?: string;\n status?: string;\n }>;\n success?: boolean;\n summary?: {\n created?: number;\n failed?: number;\n total?: number;\n updated?: number;\n };\n};\n}\n\n/**\n * Batch update screen orientation\n * POST /v1/partner/devices/batch/orientation\n */\nexport interface batchUpdateDeviceOrientationOperation {\n readonly path: \"/v1/partner/devices/batch/orientation\";\n readonly method: \"POST\";\n request: {\n device_ids: Array<string>;\n screen_orientation: \"portrait\" | \"landscape\" | \"mixed\" | \"unknown\";\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n matched_devices?: number;\n missing_device_ids?: Array<string>;\n success?: boolean;\n total_devices?: number;\n updated_screens?: number;\n};\n}\n\n/**\n * Reconcile device fleet\n * POST /v1/partner/devices/reconcile\n */\nexport interface reconcileDevicesOperation {\n readonly path: \"/v1/partner/devices/reconcile\";\n readonly method: \"POST\";\n request: {\n /** Your device external IDs to reconcile. */\n external_ids: Array<string>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n duplicate?: Array<Record<string, unknown>>;\n malformed?: Array<Record<string, unknown>>;\n matched?: Array<Record<string, unknown>>;\n missing?: Array<string>;\n orphan?: Array<Record<string, unknown>>;\n success?: boolean;\n summary?: {\n duplicate?: number;\n malformed?: number;\n matched?: number;\n missing?: number;\n orphan?: number;\n registered_active?: number;\n requested?: number;\n };\n truncated?: Record<string, unknown>;\n};\n}\n\n/**\n * Browsable HTML documentation index\n * GET /v1/partner/docs\n */\nexport interface partnerDocsBrowserOperation {\n readonly path: \"/v1/partner/docs\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: unknown;\n}\n\n/**\n * Get earnings summary\n * GET /v1/partner/earnings\n */\nexport interface getEarningsOperation {\n readonly path: \"/v1/partner/earnings\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: EarningsResponse;\n success?: boolean;\n};\n}\n\n/**\n * Get detailed earnings breakdown\n * GET /v1/partner/earnings/breakdown\n */\nexport interface getEarningsBreakdownOperation {\n readonly path: \"/v1/partner/earnings/breakdown\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n end_date?: string;\n group_by?: \"day\" | \"week\" | \"screen\";\n start_date?: string;\n};\n response: {\n data?: EarningsBreakdownResponse;\n success?: boolean;\n};\n}\n\n/**\n * Request a payout\n * POST /v1/partner/earnings/payout\n */\nexport interface requestPayoutOperation {\n readonly path: \"/v1/partner/earnings/payout\";\n readonly method: \"POST\";\n request: {\n /** Amount to payout in cents (optional, defaults to available balance) */\n amount_cents?: number;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: PayoutResponse;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get transaction history\n * GET /v1/partner/earnings/transactions\n */\nexport interface getTransactionsOperation {\n readonly path: \"/v1/partner/earnings/transactions\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n offset?: number;\n type?: \"all\" | \"earning\" | \"payout\";\n};\n response: {\n data?: {\n pagination?: Pagination;\n transactions?: Array<Transaction>;\n };\n success?: boolean;\n};\n}\n\n/**\n * List active SSE connections\n * GET /v1/partner/events/connections\n */\nexport interface getActiveSSEConnectionsOperation {\n readonly path: \"/v1/partner/events/connections\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n connections?: Array<{\n connected_at?: string;\n connection_id?: string;\n ip?: string;\n subscribed_events?: Array<string>;\n }>;\n count?: number;\n max_connections?: number;\n object?: string;\n};\n}\n\n/**\n * Subscribe to real-time events (SSE)\n * GET /v1/partner/events/stream\n */\nexport interface subscribeToEventsOperation {\n readonly path: \"/v1/partner/events/stream\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n events?: string;\n};\n response: unknown;\n}\n\n/**\n * Poll a fleet-command job\n * GET /v1/partner/fleet-commands/{id}\n */\nexport interface getFleetCommandOperation {\n readonly path: \"/v1/partner/fleet-commands/{id}\";\n readonly method: \"GET\";\n request: never;\n params: {\n id: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n fleet_command?: FleetCommand;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get fleet analytics\n * GET /v1/partner/fleet/analytics\n */\nexport interface getFleetAnalyticsOperation {\n readonly path: \"/v1/partner/fleet/analytics\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Average attention score (0-1) over the fleet's audience windows in the last 24 hours. Omitted when it was not measured — no screen in scope had an audience window, or the audience read failed (see `avg_attention_outcome`); never a `0` that stands in for a missing measurement. */\n avg_attention?: number;\n /** Whether the attention average above was measured. `kind: ok` — the read ran (the average is omitted only when no screen had an audience window); `kind: unavailable` — the audience read failed, so the average is omitted. Independent of the other fields, which are served either way. */\n avg_attention_outcome?: {\n kind?: \"ok\" | \"unavailable\";\n message?: string;\n };\n /** Average CPM in dollars */\n avg_cpm?: number;\n /** Fill rate percentage (0-100) */\n fill_rate_pct?: number;\n impressions_24h?: number;\n screen_count?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Push command to fleet\n * POST /v1/partner/fleet/command\n */\nexport interface pushFleetCommandOperation {\n readonly path: \"/v1/partner/fleet/command\";\n readonly method: \"POST\";\n request: {\n command: {\n /** Command-specific data */\n payload?: Record<string, unknown>;\n type: \"reload\" | \"update_settings\" | \"restart\" | \"screenshot\" | \"clear_cache\" | \"update_content\" | \"refresh_ads\" | \"update_config\" | \"get_state\" | \"show_ad\" | \"play_ad\";\n };\n /** Optional filter to target specific devices */\n filter?: {\n country?: string;\n /** A screen group id (sg_) whose members are targeted (unioned with screenIds). */\n group?: string;\n screenIds?: Array<string>;\n venueType?: string;\n };\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n errors?: Array<{\n error?: string;\n screenId?: string;\n }>;\n /** Devices successfully queued */\n queued?: number;\n /** Total devices matching filter */\n targeted?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get fleet overview\n * GET /v1/partner/fleet/overview\n */\nexport interface getFleetOverviewOperation {\n readonly path: \"/v1/partner/fleet/overview\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n countries?: Record<string, number>;\n offline?: number;\n online?: number;\n programmatic_enabled?: number;\n total_devices?: number;\n venue_types?: Record<string, number>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Update fleet-wide settings\n * POST /v1/partner/fleet/settings\n */\nexport interface updateFleetSettingsOperation {\n readonly path: \"/v1/partner/fleet/settings\";\n readonly method: \"POST\";\n request: {\n filter?: {\n country?: string;\n /** A screen group id (sg_) whose members are targeted (unioned with screenIds). */\n group?: string;\n screenIds?: Array<string>;\n venueType?: string;\n };\n /** Settings to update (merged with existing) */\n settings: {\n content_preferences?: Record<string, unknown>;\n display_settings?: Record<string, unknown>;\n header_bidding_settings?: {\n allocation?: number;\n enabled?: boolean;\n force_request?: boolean;\n min_interval_seconds?: number;\n };\n };\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n errors?: Array<Record<string, unknown>>;\n targeted?: number;\n updated?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Record impression (pixel)\n * GET /v1/partner/impression\n */\nexport interface recordImpressionGetOperation {\n readonly path: \"/v1/partner/impression\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n adid: string;\n aid?: string;\n completed?: boolean;\n did?: string;\n display_area_ratio?: number;\n display_mode?: \"fullscreen\" | \"l-bar\" | \"pip\";\n duration?: number;\n impid: string;\n lbar_position?: \"bottom\" | \"right\";\n lbar_size?: number;\n lbar_slot_count?: number;\n lbar_slot_index?: number;\n sid?: string;\n sig?: string;\n sig_ts?: number;\n};\n response: {\n duplicate?: boolean;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Record impression (POST)\n * POST /v1/partner/impression\n */\nexport interface recordImpressionPostOperation {\n readonly path: \"/v1/partner/impression\";\n readonly method: \"POST\";\n request: {\n adid: string;\n aid?: string;\n completed?: boolean;\n did?: string;\n display_area_ratio?: number;\n display_mode?: \"fullscreen\" | \"l-bar\" | \"pip\";\n duration?: number;\n impid: string;\n lbar_position?: \"bottom\" | \"right\";\n lbar_size?: number;\n lbar_slot_count?: number;\n lbar_slot_index?: number;\n sid?: string;\n sig?: string;\n sig_ts?: number;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n duplicate?: boolean;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Batch record impressions\n * POST /v1/partner/impressions/batch\n */\nexport interface recordImpressionsBatchOperation {\n readonly path: \"/v1/partner/impressions/batch\";\n readonly method: \"POST\";\n request: {\n impressions: Array<{\n adid: string;\n aid?: string;\n completed?: boolean;\n device_fingerprint?: string;\n did?: string;\n display_area_ratio?: number;\n display_mode?: \"fullscreen\" | \"l-bar\" | \"pip\";\n duration?: number;\n impid: string;\n lbar_position?: \"bottom\" | \"right\";\n lbar_size?: number;\n lbar_slot_count?: number;\n lbar_slot_index?: number;\n sid?: string;\n sig?: string;\n sig_ts?: number;\n timestamp?: string;\n }>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n errors?: Array<{\n error?: string;\n impid?: string;\n }>;\n failed?: number;\n recorded?: number;\n skipped?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get partner info\n * GET /v1/partner/info\n */\nexport interface getPartnerInfoOperation {\n readonly path: \"/v1/partner/info\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: Partner;\n success?: boolean;\n};\n}\n\n/**\n * List intent categories\n * GET /v1/partner/intent/categories\n */\nexport interface listIntentCategoriesOperation {\n readonly path: \"/v1/partner/intent/categories\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n count?: number;\n data?: Array<{\n description?: string;\n id?: string;\n /** Minimum intent score to match this category */\n min_intent_score?: number;\n name?: string;\n /** CPM multiplier applied when this intent is detected */\n premium_multiplier?: number;\n purchase_stages?: Array<\"READY_TO_BUY\" | \"COMPARING\" | \"CONSIDERING\" | \"BROWSING\">;\n }>;\n object?: string;\n};\n}\n\n/**\n * Get intent category details\n * GET /v1/partner/intent/categories/{categoryId}\n */\nexport interface getIntentCategoryOperation {\n readonly path: \"/v1/partner/intent/categories/{categoryId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n categoryId: string;\n};\n query: Record<string, unknown>;\n response: {\n description?: string;\n iab_segments?: Array<Record<string, unknown>>;\n id?: string;\n min_intent_score?: number;\n name?: string;\n object?: string;\n premium_multiplier?: number;\n purchase_stages?: Array<string>;\n triggers?: Array<string>;\n};\n}\n\n/**\n * Get intent-based pricing for a screen\n * GET /v1/partner/intent/pricing/{screenId}\n */\nexport interface getIntentPricingOperation {\n readonly path: \"/v1/partner/intent/pricing/{screenId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n category_match?: {\n category_id?: string;\n category_name?: string;\n intent_score?: number;\n premium_multiplier?: number;\n purchase_stage?: string;\n } | null;\n current_intent?: {\n categories?: Array<string>;\n purchase_stage?: \"NONE\" | \"BROWSING\" | \"CONSIDERING\" | \"COMPARING\" | \"READY_TO_BUY\";\n /** Intent confidence score (0-1) */\n score?: number;\n /** Whether an ad swap should be triggered */\n should_trigger?: boolean;\n };\n object?: string;\n pricing?: {\n base_cpm?: number;\n category_id?: string;\n category_name?: string;\n /** Effective CPM after intent premium */\n intent_cpm?: number;\n /** Additional CPM from intent detection */\n intent_premium?: number;\n premium_multiplier?: number;\n };\n screen_id?: string;\n};\n}\n\n/**\n * Trillboards measurement methodology disclosure\n * GET /v1/partner/measurement-methodology\n */\nexport interface getMeasurementMethodologyOperation {\n readonly path: \"/v1/partner/measurement-methodology\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n format?: \"json\";\n};\n response: MeasurementMethodology;\n}\n\n/**\n * Measurement methodology disclosure (JSON, no negotiation)\n * GET /v1/partner/measurement-methodology.json\n */\nexport interface getMeasurementMethodologyJsonOperation {\n readonly path: \"/v1/partner/measurement-methodology.json\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: MeasurementMethodology;\n}\n\n/**\n * Get network configuration\n * GET /v1/partner/network-config\n */\nexport interface getNetworkConfigOperation {\n readonly path: \"/v1/partner/network-config\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n network_config?: PartnerNetworkConfig;\n object?: string;\n parent_partner_id?: string | null;\n};\n}\n\n/**\n * Partial update network configuration\n * PATCH /v1/partner/network-config\n */\nexport interface patchNetworkConfigOperation {\n readonly path: \"/v1/partner/network-config\";\n readonly method: \"PATCH\";\n request: PartnerNetworkConfigUpdate;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n network_config?: PartnerNetworkConfig;\n object?: string;\n parent_partner_id?: string | null;\n};\n}\n\n/**\n * Update network configuration\n * PUT /v1/partner/network-config\n */\nexport interface updateNetworkConfigOperation {\n readonly path: \"/v1/partner/network-config\";\n readonly method: \"PUT\";\n request: PartnerNetworkConfigUpdate;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n network_config?: PartnerNetworkConfig;\n object?: string;\n parent_partner_id?: string | null;\n};\n}\n\n/**\n * Network-wide audience and inventory trends\n * GET /v1/partner/network/trends\n */\nexport interface getNetworkTrendsOperation {\n readonly path: \"/v1/partner/network/trends\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n days?: number;\n};\n response: {\n audience_shift?: Array<Record<string, unknown>>;\n days?: number;\n venue_type_mix?: Array<Record<string, unknown>>;\n};\n}\n\n/**\n * Unauthenticated liveness probe\n * GET /v1/partner/ping\n */\nexport interface partnerPingOperation {\n readonly path: \"/v1/partner/ping\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n ok?: boolean;\n service?: string;\n};\n}\n\n/**\n * Authenticated liveness probe\n * GET /v1/partner/ping/auth\n */\nexport interface partnerPingAuthOperation {\n readonly path: \"/v1/partner/ping/auth\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n ok?: boolean;\n partner_id?: string;\n tier?: \"basic\" | \"developer\" | \"enterprise\";\n};\n}\n\n/**\n * Record a log-level play\n * POST /v1/partner/plays\n */\nexport interface recordPartnerPlayOperation {\n readonly path: \"/v1/partner/plays\";\n readonly method: \"POST\";\n request: PartnerPlay;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n /** The content hash the play named. Absent when the play sent no creative_ref. */\n content_sha256?: string;\n object?: string;\n play_id?: string;\n status?: \"accepted\" | \"duplicate\";\n};\n}\n\n/**\n * Record a batch of log-level plays\n * POST /v1/partner/plays/batch\n */\nexport interface recordPartnerPlaysBatchOperation {\n readonly path: \"/v1/partner/plays/batch\";\n readonly method: \"POST\";\n request: {\n plays: Array<PartnerPlay>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n accepted?: number;\n duplicate?: number;\n errors?: Array<{\n play_id?: string | null;\n reason?: string;\n }>;\n object?: string;\n rejected?: number;\n total?: number;\n};\n}\n\n/**\n * Get machine-readable pricing\n * GET /v1/partner/pricing\n */\nexport interface getPricingOperation {\n readonly path: \"/v1/partner/pricing\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n agent_register_url?: string;\n billing_setup_url?: string;\n /** Volume discount tiers for prepaid credits */\n committed_use?: Record<string, {\n credits_usd?: number;\n discount_pct?: number;\n price_usd?: number;\n }>;\n currency?: string;\n description?: string;\n /** Free tier limits per product and meter */\n free_tiers?: Record<string, unknown>;\n model?: string;\n object?: string;\n /** Pricing details per product */\n products?: {\n attribution?: {\n description?: string;\n meters?: Record<string, {\n first_campaign_free?: boolean;\n per_unit?: string;\n unit_price_usd?: number;\n }>;\n name?: string;\n };\n data_api?: {\n description?: string;\n name?: string;\n notes?: string;\n tiers?: Array<{\n unit_price_usd?: number;\n /** null means unlimited (final tier) */\n up_to?: number | null;\n }>;\n unit?: string;\n };\n data_marketplace?: {\n description?: string;\n meters?: Record<string, {\n free_quota_per_month?: number;\n per_unit?: string;\n unit_price_usd?: number;\n }>;\n name?: string;\n notes?: string;\n revenue_share?: Record<string, unknown>;\n };\n fein_edge_ai?: {\n description?: string;\n name?: string;\n pricing?: string;\n };\n partner_platform?: {\n description?: string;\n name?: string;\n pricing?: string;\n revenue_share?: {\n distributor?: number;\n earner?: number;\n platform?: number;\n };\n };\n programmatic?: {\n description?: string;\n name?: string;\n pricing?: string;\n };\n proof_of_play?: {\n description?: string;\n name?: string;\n notes?: string;\n tiers?: Array<{\n unit_price_usd?: number;\n up_to?: number | null;\n }>;\n unit?: string;\n };\n };\n request_id?: string;\n};\n}\n\n/**\n * Record programmatic ad event (legacy SDK alias)\n * POST /v1/partner/programmatic-event\n */\nexport interface recordProgrammaticEventLegacyAliasOperation {\n readonly path: \"/v1/partner/programmatic-event\";\n readonly method: \"POST\";\n request: {\n ad_id?: string;\n /** Device fingerprint or external device ID */\n device_id?: string;\n /** Short legacy device identifier alias */\n did?: string;\n error_code?: string;\n error_message?: string;\n event: \"ad_started\" | \"ad_ended\" | \"no_fill\" | \"error\";\n reason?: string;\n request_id?: string;\n type?: \"ad_started\" | \"ad_ended\" | \"no_fill\" | \"error\";\n variant_name?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Quick start — register partner and first device in one call\n * POST /v1/partner/quick-start\n */\nexport interface quickStartOperation {\n readonly path: \"/v1/partner/quick-start\";\n readonly method: \"POST\";\n request: {\n /** Company or partner name */\n company_name: string;\n /** Display configuration for the first device */\n device_display?: Record<string, unknown>;\n /** Location for the first device */\n device_location?: Record<string, unknown>;\n /** Name for the first device */\n device_name?: string;\n email: string;\n /** Optional legacy classification. Omit for the default self-serve Partner API key. */\n partner_type?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n credentials?: {\n /** Store securely — shown only once! */\n api_key?: string;\n api_key_prefix?: string;\n warning?: string;\n };\n device?: {\n device_id?: string;\n embed_url?: string;\n external_device_id?: string;\n fingerprint?: string;\n name?: string;\n screen_id?: string;\n status?: string;\n };\n integration?: {\n api_base?: string;\n docs_url?: string;\n embed_url?: string;\n iframe_snippet?: string;\n sdk_snippet?: string;\n sdk_url?: string;\n };\n next_steps?: Array<string>;\n partner?: {\n name?: string;\n partner_id?: string;\n revenue_share_percent?: number;\n slug?: string;\n status?: string;\n };\n sdk_config?: {\n ad_interval?: number;\n allowed_ad_types?: Array<string>;\n cache_size?: number;\n image_duration?: number;\n max_ads_per_hour?: number;\n sound_enabled?: boolean;\n };\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Register a new partner\n * POST /v1/partner/register\n */\nexport interface registerPartnerOperation {\n readonly path: \"/v1/partner/register\";\n readonly method: \"POST\";\n request: {\n /** Whitelist of allowed referrer domains */\n allowed_domains?: Array<string>;\n contact_email: string;\n contact_name?: string;\n /** Company or partner name */\n name: string;\n /** Optional legacy classification. Omit this field — it does not change which API key you receive. Existing integrations may continue sending historical values (including `ctv_publisher`) for backward compatibility; every partner_type mints the same self-serve Partner API key. */\n partner_type?: \"vending_machine\" | \"kiosk\" | \"digital_signage\" | \"retail_display\" | \"ctv_publisher\" | \"other\";\n /** Partner's revenue share percentage (negotiated per partner) */\n revenue_share_percent?: number;\n /** URL-safe identifier (lowercase, no spaces) */\n slug: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Store this securely - only shown once. This ONE self-serve Partner API key authenticates the general Partner API surface in this spec AND the CTV Measurement SDK ingestion surface (`/v2/sdk/*`, `/v2/csi`) — it carries `sdk:*` and `mcp:tools` by default, so no separate CTV or MCP key is issued. Reuse it during OpenRTB onboarding to add buyer capabilities. */\n api_key?: string;\n partner_id?: string;\n /** Optional portal onboarding details for the partner owner account */\n portal_access?: {\n claim_complete_endpoint?: string;\n claim_request_endpoint?: string;\n claim_required?: boolean;\n claim_verify_endpoint?: string;\n email?: string;\n message?: string;\n new_account?: boolean;\n portal_auth_url?: string;\n portal_url?: string;\n user_id?: string;\n };\n revenue_share_percent?: number;\n slug?: string;\n status?: \"pending\" | \"active\";\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Generate a test event\n * POST /v1/partner/sandbox/event\n */\nexport interface generateSandboxEventOperation {\n readonly path: \"/v1/partner/sandbox/event\";\n readonly method: \"POST\";\n request: {\n /** Event type to generate */\n event_type: string;\n /** Target sandbox screen (auto-selects first if omitted) */\n screen_id?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n /** Event payload (varies by event type) */\n data?: Record<string, unknown>;\n event?: string;\n event_id?: string;\n is_sandbox?: boolean;\n object?: string;\n screen_id?: string | null;\n /** Number of SSE connections that received this event */\n sse_delivered?: number;\n timestamp?: string;\n};\n}\n\n/**\n * Reset sandbox data\n * POST /v1/partner/sandbox/reset\n */\nexport interface resetSandboxOperation {\n readonly path: \"/v1/partner/sandbox/reset\";\n readonly method: \"POST\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n events_cleared?: number;\n object?: string;\n reset_at?: string;\n screens_cleared?: number;\n success?: boolean;\n};\n}\n\n/**\n * Create a sandbox screen\n * POST /v1/partner/sandbox/screen\n */\nexport interface createSandboxScreenOperation {\n readonly path: \"/v1/partner/sandbox/screen\";\n readonly method: \"POST\";\n request: {\n /** Custom screen name */\n name?: string;\n /** Override venue type */\n venue_type?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n audience?: {\n avg_dwell_time_sec?: number;\n current_viewers?: number;\n demographics?: Record<string, unknown>;\n };\n created_at?: string;\n device_id?: string;\n display_settings?: Record<string, unknown>;\n fingerprint?: string;\n id?: string;\n is_sandbox?: boolean;\n last_heartbeat?: string;\n location?: {\n city?: string;\n latitude?: number;\n longitude?: number;\n name?: string;\n state?: string;\n venue_type?: string;\n };\n name?: string;\n object?: string;\n partner_id?: string;\n screen_id?: string;\n status?: string;\n};\n}\n\n/**\n * Get sandbox status\n * GET /v1/partner/sandbox/status\n */\nexport interface getSandboxStatusOperation {\n readonly path: \"/v1/partner/sandbox/status\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n last_activity?: string | null;\n object?: string;\n recent_events?: Array<{\n event?: string;\n event_id?: string;\n timestamp?: string;\n }>;\n sandbox_screens?: Array<{\n created_at?: string;\n id?: string;\n last_heartbeat?: string;\n location?: Record<string, unknown>;\n name?: string;\n status?: string;\n }>;\n total_screens?: number;\n total_test_events?: number;\n};\n}\n\n/**\n * List screen groups\n * GET /v1/partner/screen-groups\n */\nexport interface listScreenGroupsOperation {\n readonly path: \"/v1/partner/screen-groups\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n offset?: number;\n};\n response: {\n data?: {\n has_more?: boolean;\n object?: string;\n screen_groups?: Array<ScreenGroup>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Create a screen group\n * POST /v1/partner/screen-groups\n */\nexport interface createScreenGroupOperation {\n readonly path: \"/v1/partner/screen-groups\";\n readonly method: \"POST\";\n request: {\n description?: string;\n name: string;\n screen_ids?: Array<string>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n screen_group?: ScreenGroup;\n };\n success?: boolean;\n};\n}\n\n/**\n * Delete (archive) a screen group\n * DELETE /v1/partner/screen-groups/{groupId}\n */\nexport interface deleteScreenGroupOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n deleted?: boolean;\n id?: string;\n object?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Retrieve a screen group\n * GET /v1/partner/screen-groups/{groupId}\n */\nexport interface getScreenGroupOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n screen_group?: ScreenGroup;\n };\n success?: boolean;\n};\n}\n\n/**\n * Update a screen group\n * POST /v1/partner/screen-groups/{groupId}\n */\nexport interface updateScreenGroupOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}\";\n readonly method: \"POST\";\n request: {\n description?: string;\n name?: string;\n};\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n screen_group?: ScreenGroup;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get analytics for a screen group\n * GET /v1/partner/screen-groups/{groupId}/analytics\n */\nexport interface getScreenGroupAnalyticsOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/analytics\";\n readonly method: \"GET\";\n request: never;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n /** Fleet analytics scoped to the group's live member screens — the same payload as GET /fleet/analytics (which also returns an average attention score alongside the fields below). */\n data?: {\n avg_cpm?: number;\n fill_rate_pct?: number;\n group_id?: string;\n impressions_24h?: number;\n screen_count?: number;\n [key: string]: unknown;\n };\n success?: boolean;\n};\n}\n\n/**\n * Run a fleet command against a screen group\n * POST /v1/partner/screen-groups/{groupId}/commands\n */\nexport interface createScreenGroupCommandOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/commands\";\n readonly method: \"POST\";\n request: {\n command: {\n payload?: Record<string, unknown>;\n type: \"reload\" | \"update_settings\" | \"restart\" | \"screenshot\" | \"clear_cache\" | \"update_content\" | \"refresh_ads\" | \"update_config\" | \"get_state\" | \"show_ad\" | \"play_ad\";\n };\n};\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n fleet_command?: FleetCommand;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get a screen group's content policy\n * GET /v1/partner/screen-groups/{groupId}/content-policy\n */\nexport interface getScreenGroupContentPolicyOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/content-policy\";\n readonly method: \"GET\";\n request: never;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n content_policy?: ScreenGroupContentPolicy;\n };\n success?: boolean;\n};\n}\n\n/**\n * Set a screen group's content policy\n * PUT /v1/partner/screen-groups/{groupId}/content-policy\n */\nexport interface updateScreenGroupContentPolicyOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/content-policy\";\n readonly method: \"PUT\";\n request: ScreenGroupContentPolicyInput;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n content_policy?: ScreenGroupContentPolicy;\n };\n success?: boolean;\n};\n}\n\n/**\n * Remove members from a screen group\n * DELETE /v1/partner/screen-groups/{groupId}/members\n */\nexport interface removeScreenGroupMembersOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/members\";\n readonly method: \"DELETE\";\n request: {\n screen_ids: Array<string>;\n};\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n removed?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * List a screen group's members\n * GET /v1/partner/screen-groups/{groupId}/members\n */\nexport interface listScreenGroupMembersOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/members\";\n readonly method: \"GET\";\n request: never;\n params: {\n groupId: string;\n};\n query: {\n limit?: number;\n offset?: number;\n};\n response: {\n data?: {\n data?: Array<{\n id?: string;\n object?: string;\n }>;\n has_more?: boolean;\n object?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Add members to a screen group\n * POST /v1/partner/screen-groups/{groupId}/members\n */\nexport interface addScreenGroupMembersOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/members\";\n readonly method: \"POST\";\n request: {\n screen_ids: Array<string>;\n};\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n added?: number;\n rejected?: Array<{\n id?: string;\n reason?: string;\n }>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get a screen group's sensing config\n * GET /v1/partner/screen-groups/{groupId}/sensing-config\n */\nexport interface getScreenGroupSensingConfigOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/sensing-config\";\n readonly method: \"GET\";\n request: never;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n sensing_config?: ScreenGroupSensingConfig;\n };\n success?: boolean;\n};\n}\n\n/**\n * Set a screen group's sensing config\n * PUT /v1/partner/screen-groups/{groupId}/sensing-config\n */\nexport interface updateScreenGroupSensingConfigOperation {\n readonly path: \"/v1/partner/screen-groups/{groupId}/sensing-config\";\n readonly method: \"PUT\";\n request: ScreenGroupSensingConfigInput;\n params: {\n groupId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n sensing_config?: ScreenGroupSensingConfig;\n };\n success?: boolean;\n};\n}\n\n/**\n * OpenRTB SDA payload for a screen\n * GET /v1/partner/screen/{external_code}/audience-segments\n */\nexport interface getScreenAudienceSegmentsOperation {\n readonly path: \"/v1/partner/screen/{external_code}/audience-segments\";\n readonly method: \"GET\";\n request: never;\n params: {\n external_code: string;\n};\n query: Record<string, unknown>;\n response: {\n external_code?: string;\n segtax_4?: Array<{\n confidence?: number;\n id?: string;\n }>;\n segtax_600?: Array<{\n confidence?: number;\n id?: string;\n }>;\n};\n}\n\n/**\n * Batch update content preferences\n * POST /v1/partner/screens/batch/content-preferences\n */\nexport interface batchUpdateContentPreferencesOperation {\n readonly path: \"/v1/partner/screens/batch/content-preferences\";\n readonly method: \"POST\";\n request: {\n preferences?: ContentPreferencesInput;\n /** Preset ID to apply (alternative to preferences) */\n preset_id?: string;\n /** Array of screen IDs to update */\n screen_ids: Array<string>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: BatchResult;\n}\n\n/**\n * Batch update display settings\n * POST /v1/partner/screens/batch/display-settings\n */\nexport interface batchUpdateDisplaySettingsOperation {\n readonly path: \"/v1/partner/screens/batch/display-settings\";\n readonly method: \"POST\";\n request: {\n screen_ids: Array<string>;\n settings: DisplaySettingsInput;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: BatchResult;\n}\n\n/**\n * Batch update programmatic settings\n * POST /v1/partner/screens/batch/programmatic-settings\n */\nexport interface batchUpdateProgrammaticSettingsOperation {\n readonly path: \"/v1/partner/screens/batch/programmatic-settings\";\n readonly method: \"POST\";\n request: {\n screen_ids: Array<string>;\n settings: ProgrammaticSettingsInput;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: BatchResult;\n}\n\n/**\n * Resolve screen ID by device identifier\n * GET /v1/partner/screens/resolve\n */\nexport interface resolvePartnerScreenOperation {\n readonly path: \"/v1/partner/screens/resolve\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n device_id?: string;\n external_device_id?: string;\n fingerprint?: string;\n};\n response: {\n data?: {\n device_id?: string;\n external_device_id?: string;\n fingerprint?: string;\n screen_id?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get per-screen analytics\n * GET /v1/partner/screens/{screenId}/analytics\n */\nexport interface getScreenAnalyticsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n period?: \"7d\" | \"30d\" | \"90d\";\n};\n response: {\n data?: ScreenAnalyticsResponse;\n success?: boolean;\n};\n}\n\n/**\n * Current atmosphere (mood, noise, crowd density)\n * GET /v1/partner/screens/{screenId}/analytics/atmosphere\n */\nexport interface getPartnerScreenAtmosphereOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics/atmosphere\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n crowdDensity?: string | null;\n currentViewers?: number | null;\n dataAge?: string;\n engagement?: number | null;\n lastUpdated?: string | null;\n mood?: string | null;\n noiseLevel?: string | null;\n };\n status?: boolean;\n};\n}\n\n/**\n * Time-bucketed venue intelligence with Lincoln-Petersen dedup\n * GET /v1/partner/screens/{screenId}/analytics/intelligence\n */\nexport interface getPartnerScreenIntelligenceOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics/intelligence\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n from: string;\n interval?: \"5min\" | \"15min\" | \"1h\" | \"1d\";\n to: string;\n};\n response: {\n data?: {\n dedup?: {\n camera_count?: number;\n method?: string;\n raw_total?: number;\n unique_total?: number;\n };\n from?: string;\n interval?: string;\n screen_id?: string;\n summary?: {\n avg_attention?: number;\n buckets?: number;\n total_unique_viewers?: number;\n };\n timeseries?: Array<Record<string, unknown>>;\n to?: string;\n };\n status?: boolean;\n};\n}\n\n/**\n * Per-screen taste profile + top-5 creative categories\n * GET /v1/partner/screens/{screenId}/analytics/taste-profile\n */\nexport interface getPartnerScreenTasteProfileOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics/taste-profile\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n computedAt?: string | null;\n daypartDistribution?: Record<string, unknown> | null;\n ready?: boolean;\n sampleCount?: number;\n screenId?: string;\n tasteVersionHash?: string | null;\n topCategories?: Array<Record<string, unknown>>;\n updatedAt?: string | null;\n venueType?: string | null;\n windowEnd?: string | null;\n windowStart?: string | null;\n };\n status?: boolean;\n};\n}\n\n/**\n * Hourly foot-traffic patterns for a screen\n * GET /v1/partner/screens/{screenId}/analytics/traffic\n */\nexport interface getPartnerScreenTrafficOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics/traffic\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n days?: number;\n};\n response: {\n data?: {\n busyScore?: number;\n hourlyTraffic?: Array<Record<string, unknown>>;\n periodDays?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Verified Attention Seconds + CPM tier for a screen\n * GET /v1/partner/screens/{screenId}/analytics/vas\n */\nexport interface getPartnerScreenVASOperation {\n readonly path: \"/v1/partner/screens/{screenId}/analytics/vas\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n avgAttentionScore?: number;\n avgFaceCount?: number;\n avgVasWeighted?: number;\n lastMeasurement?: string | null;\n medianVas?: number;\n multiplier?: number;\n p90Vas?: number;\n tier?: string;\n totalMeasurements?: number;\n };\n status?: boolean;\n};\n}\n\n/**\n * Apply content preset to screen\n * POST /v1/partner/screens/{screenId}/apply-preset\n */\nexport interface applyContentPresetOperation {\n readonly path: \"/v1/partner/screens/{screenId}/apply-preset\";\n readonly method: \"POST\";\n request: {\n /** ID of the preset to apply */\n preset_id: string;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ContentPreferencesResponse & {\n preset_applied?: string;\n };\n}\n\n/**\n * Get real-time audience metrics\n * GET /v1/partner/screens/{screenId}/audience-metrics\n */\nexport interface getAudienceMetricsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/audience-metrics\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n include_history?: boolean;\n period?: \"1h\" | \"6h\" | \"24h\";\n};\n response: AudienceMetricsResponse;\n}\n\n/**\n * Get screen content preferences\n * GET /v1/partner/screens/{screenId}/content-preferences\n */\nexport interface getContentPreferencesOperation {\n readonly path: \"/v1/partner/screens/{screenId}/content-preferences\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ContentPreferencesResponse;\n}\n\n/**\n * Update content preferences (partial)\n * PATCH /v1/partner/screens/{screenId}/content-preferences\n */\nexport interface patchContentPreferencesOperation {\n readonly path: \"/v1/partner/screens/{screenId}/content-preferences\";\n readonly method: \"PATCH\";\n request: ContentPreferencesInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ContentPreferencesResponse;\n}\n\n/**\n * Update content preferences (full)\n * PUT /v1/partner/screens/{screenId}/content-preferences\n */\nexport interface updateContentPreferencesOperation {\n readonly path: \"/v1/partner/screens/{screenId}/content-preferences\";\n readonly method: \"PUT\";\n request: ContentPreferencesInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ContentPreferencesResponse;\n}\n\n/**\n * List default streams for a screen\n * GET /v1/partner/screens/{screenId}/default-streams\n */\nexport interface listPartnerDefaultStreamsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n current?: PartnerDefaultStream;\n streams?: Array<PartnerDefaultStream>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Create a default stream (URL-based)\n * POST /v1/partner/screens/{screenId}/default-streams\n */\nexport interface createPartnerDefaultStreamOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams\";\n readonly method: \"POST\";\n request: PartnerDefaultStreamCreate;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerDefaultStream;\n success?: boolean;\n};\n}\n\n/**\n * Upload image carousel default stream\n * POST /v1/partner/screens/{screenId}/default-streams/images\n */\nexport interface createPartnerDefaultStreamImagesOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams/images\";\n readonly method: \"POST\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerDefaultStream;\n success?: boolean;\n};\n}\n\n/**\n * Reorder default streams\n * PUT /v1/partner/screens/{screenId}/default-streams/order\n */\nexport interface reorderPartnerDefaultStreamsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams/order\";\n readonly method: \"PUT\";\n request: {\n order: Array<string>;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Upload a video default stream\n * POST /v1/partner/screens/{screenId}/default-streams/upload\n */\nexport interface createPartnerDefaultStreamUploadOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams/upload\";\n readonly method: \"POST\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerDefaultStream;\n success?: boolean;\n};\n}\n\n/**\n * Delete a default stream\n * DELETE /v1/partner/screens/{screenId}/default-streams/{streamId}\n */\nexport interface deletePartnerDefaultStreamOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams/{streamId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n screenId: string;\n streamId: string;\n};\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Update a default stream\n * PUT /v1/partner/screens/{screenId}/default-streams/{streamId}\n */\nexport interface updatePartnerDefaultStreamOperation {\n readonly path: \"/v1/partner/screens/{screenId}/default-streams/{streamId}\";\n readonly method: \"PUT\";\n request: PartnerDefaultStreamUpdate;\n params: {\n screenId: string;\n streamId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerDefaultStream;\n success?: boolean;\n};\n}\n\n/**\n * Get screen demographics\n * GET /v1/partner/screens/{screenId}/demographics\n */\nexport interface getScreenDemographicsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/demographics\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n period?: \"24h\" | \"7d\" | \"30d\";\n};\n response: DemographicsResponse;\n}\n\n/**\n * Get display settings\n * GET /v1/partner/screens/{screenId}/display-settings\n */\nexport interface getDisplaySettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/display-settings\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: DisplaySettingsResponse;\n}\n\n/**\n * Update display settings (partial)\n * PATCH /v1/partner/screens/{screenId}/display-settings\n */\nexport interface patchDisplaySettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/display-settings\";\n readonly method: \"PATCH\";\n request: DisplaySettingsInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: DisplaySettingsResponse;\n}\n\n/**\n * Update display settings (full)\n * PUT /v1/partner/screens/{screenId}/display-settings\n */\nexport interface updateDisplaySettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/display-settings\";\n readonly method: \"PUT\";\n request: DisplaySettingsInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: DisplaySettingsResponse;\n}\n\n/**\n * Get L-Bar settings\n * GET /v1/partner/screens/{screenId}/l-bar\n */\nexport interface getPartnerLBarSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/l-bar\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerLBarSettings;\n success?: boolean;\n};\n}\n\n/**\n * Update L-Bar settings\n * PUT /v1/partner/screens/{screenId}/l-bar\n */\nexport interface updatePartnerLBarSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/l-bar\";\n readonly method: \"PUT\";\n request: {\n display_preferences?: PartnerLBarDisplayPreferences;\n l_bar_content?: PartnerLBarContent;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: PartnerLBarSettings;\n success?: boolean;\n};\n}\n\n/**\n * Update L-Bar live updates\n * PUT /v1/partner/screens/{screenId}/l-bar/live-updates\n */\nexport interface updatePartnerLBarLiveUpdatesOperation {\n readonly path: \"/v1/partner/screens/{screenId}/l-bar/live-updates\";\n readonly method: \"PUT\";\n request: {\n custom_status?: string;\n promotion?: string;\n queue_number?: string;\n wait_time_minutes?: number;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n success?: boolean;\n};\n}\n\n/**\n * Get programmatic settings\n * GET /v1/partner/screens/{screenId}/programmatic-settings\n */\nexport interface getProgrammaticSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/programmatic-settings\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ProgrammaticSettingsResponse;\n}\n\n/**\n * Update programmatic settings (partial)\n * PATCH /v1/partner/screens/{screenId}/programmatic-settings\n */\nexport interface patchProgrammaticSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/programmatic-settings\";\n readonly method: \"PATCH\";\n request: ProgrammaticSettingsInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ProgrammaticSettingsResponse;\n}\n\n/**\n * Update programmatic settings (full)\n * PUT /v1/partner/screens/{screenId}/programmatic-settings\n */\nexport interface updateProgrammaticSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/programmatic-settings\";\n readonly method: \"PUT\";\n request: ProgrammaticSettingsInput;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: ProgrammaticSettingsResponse;\n}\n\n/**\n * Resolve the effective content policy for a screen\n * GET /v1/partner/screens/{screenId}/resolved-content-policy\n */\nexport interface getResolvedContentPolicyOperation {\n readonly path: \"/v1/partner/screens/{screenId}/resolved-content-policy\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: ResolvedContentPolicy;\n success?: boolean;\n};\n}\n\n/**\n * Resolve the effective sensing config for a screen\n * GET /v1/partner/screens/{screenId}/resolved-sensing-config\n */\nexport interface getResolvedSensingConfigOperation {\n readonly path: \"/v1/partner/screens/{screenId}/resolved-sensing-config\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: ResolvedSensingConfig;\n success?: boolean;\n};\n}\n\n/**\n * Read the custom sensing chips a screen is producing\n * GET /v1/partner/screens/{screenId}/sensing/chips\n */\nexport interface getScreenSensingChipsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/sensing/chips\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n days?: number;\n limit?: number;\n offset?: number;\n};\n response: {\n data?: {\n /** The custom field_keys this screen senses (from its active profile) */\n fields?: Array<{\n description?: string;\n field_key?: string;\n type?: string;\n }>;\n /** Recent chip observations (one row per field_key per observation) */\n rows?: Array<Record<string, unknown>>;\n screen_id?: string;\n total?: number;\n truncated?: boolean;\n };\n success?: boolean;\n};\n}\n\n/**\n * Generate a custom sensing profile from a natural-language intent\n * POST /v1/partner/screens/{screenId}/sensing/interpret-intent\n */\nexport interface interpretScreenSensingIntentOperation {\n readonly path: \"/v1/partner/screens/{screenId}/sensing/interpret-intent\";\n readonly method: \"POST\";\n request: {\n /** Optional device-capability hint for model selection */\n deviceTier?: string;\n /** Natural-language description of what to sense */\n intent: string;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n /** The generated profile — pass to /sensing/profile to deploy */\n profile?: Record<string, unknown>;\n screen_id?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Deploy a custom sensing profile to a screen\n * POST /v1/partner/screens/{screenId}/sensing/profile\n */\nexport interface deployScreenSensingProfileOperation {\n readonly path: \"/v1/partner/screens/{screenId}/sensing/profile\";\n readonly method: \"POST\";\n request: {\n /** The sensing profile object returned by interpret-intent */\n profile: Record<string, unknown>;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n command?: Record<string, unknown>;\n profile?: Record<string, unknown>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Read a screen's AI sensing performance settings\n * GET /v1/partner/screens/{screenId}/sensing/settings\n */\nexport interface getScreenSensingSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/sensing/settings\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: SensingSettingsResponse;\n success?: boolean;\n};\n}\n\n/**\n * Update a screen's AI sensing performance mode\n * PATCH /v1/partner/screens/{screenId}/sensing/settings\n */\nexport interface updateScreenSensingSettingsOperation {\n readonly path: \"/v1/partner/screens/{screenId}/sensing/settings\";\n readonly method: \"PATCH\";\n request: {\n performance_mode: SensingPerformanceMode;\n};\n params: {\n screenId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n command?: Record<string, unknown>;\n profile?: Record<string, unknown>;\n settings?: SensingSettingsResponse;\n };\n success?: boolean;\n};\n}\n\n/**\n * List your Sense Anything Routes\n * GET /v1/partner/sensing/routes\n */\nexport interface listPartnerSensingRoutesOperation {\n readonly path: \"/v1/partner/sensing/routes\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n includeDisabled?: boolean;\n};\n response: {\n data?: {\n routes?: Array<SensingRoute>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Create a Sense Anything Route (threshold or scheduled-digest alert)\n * POST /v1/partner/sensing/routes\n */\nexport interface createPartnerSensingRouteOperation {\n readonly path: \"/v1/partner/sensing/routes\";\n readonly method: \"POST\";\n request: CreatePartnerSensingRouteRequest;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n route?: SensingRoute;\n };\n success?: boolean;\n};\n}\n\n/**\n * Delete (soft) a Sense Anything Route\n * DELETE /v1/partner/sensing/routes/{routeId}\n */\nexport interface deletePartnerSensingRouteOperation {\n readonly path: \"/v1/partner/sensing/routes/{routeId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n routeId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n routeId?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get one Sense Anything Route\n * GET /v1/partner/sensing/routes/{routeId}\n */\nexport interface getPartnerSensingRouteOperation {\n readonly path: \"/v1/partner/sensing/routes/{routeId}\";\n readonly method: \"GET\";\n request: never;\n params: {\n routeId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n route?: SensingRoute;\n };\n success?: boolean;\n};\n}\n\n/**\n * Update a Sense Anything Route (partial, non-clobbering)\n * PATCH /v1/partner/sensing/routes/{routeId}\n */\nexport interface updatePartnerSensingRouteOperation {\n readonly path: \"/v1/partner/sensing/routes/{routeId}\";\n readonly method: \"PATCH\";\n request: UpdatePartnerSensingRouteRequest;\n params: {\n routeId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n route?: SensingRoute;\n };\n success?: boolean;\n};\n}\n\n/**\n * Create Stripe Connect account\n * POST /v1/partner/stripe/connect\n */\nexport interface createStripeConnectAccountOperation {\n readonly path: \"/v1/partner/stripe/connect\";\n readonly method: \"POST\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n account_id?: string;\n charges_enabled?: boolean;\n country?: string;\n default_currency?: string;\n details_submitted?: boolean;\n next_step?: string | null;\n payouts_enabled?: boolean;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get Stripe dashboard login link\n * GET /v1/partner/stripe/login-link\n */\nexport interface getStripeLoginLinkOperation {\n readonly path: \"/v1/partner/stripe/login-link\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n account_id?: string;\n instructions?: string;\n login_url?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get Stripe onboarding link\n * GET /v1/partner/stripe/onboarding-link\n */\nexport interface getStripeOnboardingLinkOperation {\n readonly path: \"/v1/partner/stripe/onboarding-link\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n account_id?: string;\n expires_at?: string;\n instructions?: string;\n onboarding_url?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Request a payout\n * POST /v1/partner/stripe/payout\n */\nexport interface requestStripePayoutOperation {\n readonly path: \"/v1/partner/stripe/payout\";\n readonly method: \"POST\";\n request: {\n /** Payout amount in cents (minimum $5.00) */\n amount_cents: number;\n /** Optional description for the payout */\n description?: string;\n /** Deduplication key to prevent duplicate payouts */\n idempotency_key?: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n amount_cents?: number;\n created?: string;\n currency?: string;\n description?: string;\n destination?: string;\n /** Estimated arrival date (2-3 business days) */\n estimated_arrival?: string;\n status?: \"pending\" | \"reversed\";\n transfer_id?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get payout history\n * GET /v1/partner/stripe/payouts\n */\nexport interface getStripePayoutHistoryOperation {\n readonly path: \"/v1/partner/stripe/payouts\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n limit?: number;\n};\n response: {\n data?: {\n has_more?: boolean;\n total?: number;\n transfers?: Array<{\n amount_cents?: number;\n created?: string;\n currency?: string;\n description?: string;\n status?: \"completed\" | \"reversed\";\n transfer_id?: string;\n }>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get Stripe account status\n * GET /v1/partner/stripe/status\n */\nexport interface getStripeAccountStatusOperation {\n readonly path: \"/v1/partner/stripe/status\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n account_id?: string | null;\n charges_enabled?: boolean;\n country?: string;\n default_currency?: string;\n details_submitted?: boolean;\n has_account?: boolean;\n payouts_enabled?: boolean;\n requirements?: {\n currently_due?: Array<string>;\n disabled_reason?: string | null;\n eventually_due?: Array<string>;\n past_due?: Array<string>;\n };\n status?: \"not_connected\" | \"pending\" | \"active\" | \"restricted\";\n };\n success?: boolean;\n};\n}\n\n/**\n * Stripe webhook receiver\n * POST /v1/partner/stripe/webhook\n */\nexport interface handleStripeWebhookOperation {\n readonly path: \"/v1/partner/stripe/webhook\";\n readonly method: \"POST\";\n request: Record<string, unknown>;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n event_type?: string;\n received?: boolean;\n};\n}\n\n/**\n * List team members\n * GET /v1/partner/team\n */\nexport interface listTeamMembersOperation {\n readonly path: \"/v1/partner/team\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n status?: \"active\" | \"pending\" | \"all\";\n};\n response: {\n data?: {\n members?: Array<TeamMember>;\n partner?: {\n name?: string;\n slug?: string;\n };\n total?: number;\n };\n success?: boolean;\n};\n}\n\n/**\n * Accept team invitation\n * POST /v1/partner/team/accept\n */\nexport interface acceptInvitationOperation {\n readonly path: \"/v1/partner/team/accept\";\n readonly method: \"POST\";\n request: {\n /** Invitation token from email link */\n token: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n partner?: {\n id?: string;\n name?: string;\n slug?: string;\n };\n permissions?: RolePermissions;\n portal_url?: string;\n role?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Invite a team member\n * POST /v1/partner/team/invite\n */\nexport interface inviteTeamMemberOperation {\n readonly path: \"/v1/partner/team/invite\";\n readonly method: \"POST\";\n request: {\n /** Email address of the invitee */\n email: string;\n /** Optional name of the invitee */\n name?: string;\n /** Role to assign to the team member */\n role?: \"admin\" | \"viewer\";\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: TeamInviteResponse;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get invitation details\n * GET /v1/partner/team/invite/{token}\n */\nexport interface getInvitationByTokenOperation {\n readonly path: \"/v1/partner/team/invite/{token}\";\n readonly method: \"GET\";\n request: never;\n params: {\n token: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n email?: string;\n expires_at?: string;\n invited_at?: string;\n partner?: {\n name?: string;\n slug?: string;\n };\n role?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Resend invitation\n * POST /v1/partner/team/{membershipId}/resend\n */\nexport interface resendInvitationOperation {\n readonly path: \"/v1/partner/team/{membershipId}/resend\";\n readonly method: \"POST\";\n request: never;\n params: {\n membershipId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n email?: string;\n expires_at?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Remove a team member\n * DELETE /v1/partner/team/{userId}\n */\nexport interface removeTeamMemberOperation {\n readonly path: \"/v1/partner/team/{userId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n userId: string;\n};\n query: Record<string, unknown>;\n response: {\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Change team member role\n * PATCH /v1/partner/team/{userId}/role\n */\nexport interface changeTeamMemberRoleOperation {\n readonly path: \"/v1/partner/team/{userId}/role\";\n readonly method: \"PATCH\";\n request: {\n role: \"admin\" | \"viewer\";\n};\n params: {\n userId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n new_role?: string;\n old_role?: string;\n permissions?: RolePermissions;\n user_id?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get public key for proof verification\n * GET /v1/partner/tracking/.well-known/public-key\n */\nexport interface getTrackingPublicKeyOperation {\n readonly path: \"/v1/partner/tracking/.well-known/public-key\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Signature algorithm (ed25519 for public key, sha256 for HMAC fallback) */\n algorithm?: \"ed25519\" | \"sha256\";\n documentation?: string;\n /** Key identifier for rotation support */\n keyId?: string;\n /** Hex-encoded Ed25519 public key (null if using HMAC fallback) */\n publicKey?: string | null;\n verificationUrl?: string;\n version?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * Record batch impressions with proof generation\n * POST /v1/partner/tracking/batch\n */\nexport interface recordTrackingBatchOperation {\n readonly path: \"/v1/partner/tracking/batch\";\n readonly method: \"POST\";\n request: {\n impressions: Array<{\n /** Ad identifier from VAST response */\n ad_id: string;\n /** Optional audience metrics */\n audience?: {\n /** Attention score (0-1) */\n attention?: number;\n /** How long viewers were present */\n dwell_time_ms?: number;\n /** Number of viewers detected */\n face_count?: number;\n };\n /** Partner's device identifier */\n device_id: string;\n /** Video duration played in milliseconds */\n duration_ms?: number;\n /** VAST event type */\n event?: \"start\" | \"complete\" | \"error\" | \"firstQuartile\" | \"midpoint\" | \"thirdQuartile\";\n /** Optional client-generated ID (auto-generated if not provided) */\n impression_id?: string;\n /** When the event occurred (defaults to now) */\n timestamp?: string;\n }>;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n /** Number of duplicate impressions skipped */\n duplicates?: number;\n /** Details of failed impressions (max 10 shown) */\n errors?: Array<{\n error?: string;\n impression_id?: string;\n }>;\n /** Number of impressions that failed validation */\n failed?: number;\n /** Number of impressions successfully recorded */\n processed?: number;\n /** Time to process the batch */\n processing_time_ms?: number;\n /** Cryptographic proofs for each processed impression */\n proofs?: Array<{\n impression_id?: string;\n public_key_id?: string;\n /** Ed25519 signature (or SHA256 fallback) */\n signature?: string;\n }>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Get API usage summary\n * GET /v1/partner/usage\n */\nexport interface getUsageSummaryOperation {\n readonly path: \"/v1/partner/usage\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n end_date?: string;\n start_date?: string;\n};\n response: {\n /** Average response time in milliseconds */\n avgResponseTime?: number;\n dailyBreakdown?: Array<{\n avg_response_ms?: number;\n /** The UTC day, sent as the timestamp of its midnight. */\n date?: string;\n total_calls?: number;\n total_errors?: number;\n }>;\n /** Error rate percentage (0-100) */\n errorRate?: number;\n object?: string;\n period?: {\n end_date?: string;\n start_date?: string;\n };\n topEndpoints?: Array<{\n endpoint?: string;\n method?: string;\n total_calls?: number;\n total_errors?: number;\n }>;\n totalCalls?: number;\n totalErrors?: number;\n};\n}\n\n/**\n * Get real-time usage dashboard\n * GET /v1/partner/usage/dashboard\n */\nexport interface getUsageDashboardOperation {\n readonly path: \"/v1/partner/usage/dashboard\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n last_7_days?: {\n avg_response_ms?: number;\n daily_breakdown?: Array<{\n avg_response_ms?: number;\n /** The UTC day, sent as the timestamp of its midnight. */\n date?: string;\n total_calls?: number;\n total_errors?: number;\n }>;\n error_rate?: number;\n total_calls?: number;\n total_errors?: number;\n };\n object?: string;\n rate_limit?: {\n current_rate?: number;\n max_rate?: number;\n percent_used?: number;\n window_reset?: string;\n window_seconds?: number;\n };\n today?: {\n avg_response_ms?: number;\n error_rate?: number;\n top_endpoints?: Array<{\n avg_response_ms?: number;\n endpoint?: string;\n method?: string;\n total_calls?: number;\n total_errors?: number;\n }>;\n total_calls?: number;\n total_errors?: number;\n };\n};\n}\n\n/**\n * Get rate limit status\n * GET /v1/partner/usage/rate-limit\n */\nexport interface getRateLimitStatusOperation {\n readonly path: \"/v1/partner/usage/rate-limit\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n current_rate?: number;\n max_rate?: number;\n object?: string;\n percent_used?: number;\n tier?: \"basic\" | \"developer\" | \"enterprise\";\n window_reset?: string;\n window_seconds?: number;\n};\n}\n\n/**\n * Get VAST configuration for direct Google integration\n * GET /v1/partner/vast/config\n */\nexport interface getVastConfigOperation {\n readonly path: \"/v1/partner/vast/config\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n device_id?: string;\n screen_id?: string;\n};\n response: {\n data?: {\n /** Google Ad Manager ad unit path */\n ad_unit?: string;\n /** IAB content categories to block */\n blocked_categories?: Array<string>;\n /** How long to cache this config */\n cache_ttl_seconds?: number;\n /** Floor price in cents (CPM) */\n floor_price_cpm?: number;\n integration_guide?: string;\n parameters?: {\n height?: number;\n /** Maximum ad duration in seconds */\n max_duration?: number;\n /** Minimum ad duration in seconds */\n min_duration?: number;\n /** Venue type code for targeting */\n venuetype?: number;\n width?: number;\n };\n /** Recommended VAST fetch timeout */\n timeout_ms?: number;\n tracking?: {\n batch_endpoint?: string;\n max_batch_size?: number;\n single_endpoint?: string;\n };\n /** Configuration variant name for debugging */\n variant?: string;\n /** VAST URL template with placeholders for {width} and {height}. Partner replaces placeholders and appends correlator. */\n vast_tag_template?: string;\n };\n success?: boolean;\n};\n}\n\n/**\n * List nearby Trillboards venues\n * GET /v1/partner/venues/nearby\n */\nexport interface getVenuesNearbyOperation {\n readonly path: \"/v1/partner/venues/nearby\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: {\n lat: number;\n lng: number;\n radius_km?: number;\n};\n response: {\n count?: number;\n venues?: Array<{\n distance_m?: number;\n screen_id?: string;\n venue_type?: string;\n }>;\n};\n}\n\n/**\n * Real-world atmosphere signals for a venue\n * GET /v1/partner/venues/{screenId}/atmosphere\n */\nexport interface getVenueAtmosphereOperation {\n readonly path: \"/v1/partner/venues/{screenId}/atmosphere\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n days?: number;\n};\n response: {\n days?: number;\n dominant_ambience?: string;\n lighting_band?: \"dark\" | \"dim\" | \"lit\" | \"bright\";\n motion_intensity?: number;\n noise_level_db?: number;\n screen_id?: string;\n};\n}\n\n/**\n * Foot-traffic profile for a venue\n * GET /v1/partner/venues/{screenId}/traffic\n */\nexport interface getVenueTrafficOperation {\n readonly path: \"/v1/partner/venues/{screenId}/traffic\";\n readonly method: \"GET\";\n request: never;\n params: {\n screenId: string;\n};\n query: {\n days?: number;\n};\n response: {\n daily?: Array<Record<string, unknown>>;\n hourly?: Array<{\n face_count_avg?: number;\n hour?: number;\n }>;\n screen_id?: string;\n};\n}\n\n/**\n * Get venue intelligence report\n * GET /v1/partner/venues/{venueId}/intelligence\n */\nexport interface getVenueIntelligenceOperation {\n readonly path: \"/v1/partner/venues/{venueId}/intelligence\";\n readonly method: \"GET\";\n request: never;\n params: {\n venueId: string;\n};\n query: {\n from: string;\n interval?: \"5min\" | \"15min\" | \"1h\" | \"1d\";\n to: string;\n};\n response: {\n dedup?: {\n camera_count?: number;\n method?: \"lincoln_petersen\" | \"single_camera\";\n raw_total?: number;\n unique_total?: number;\n };\n from?: string;\n interval?: string;\n summary?: {\n avg_attention?: number;\n buckets?: number;\n total_unique_viewers?: number;\n };\n timeseries?: Array<{\n age_distribution?: Record<string, number>;\n avg_attention?: number;\n avg_engagement?: number;\n avg_noise_level?: number | null;\n brand_mention_windows?: number;\n bucket?: string;\n gender_distribution?: {\n female?: number;\n male?: number;\n };\n income_level?: string | null;\n measurements?: number;\n mood?: string | null;\n peak_viewers?: number;\n raw_viewers?: number;\n screen_count?: number;\n /** Deduplicated viewer count */\n viewers?: number;\n weather?: {\n condition?: string | null;\n is_raining?: boolean;\n temperature_f?: number | null;\n };\n }>;\n to?: string;\n venue_id?: string;\n};\n}\n\n/**\n * List webhooks\n * GET /v1/partner/webhooks\n */\nexport interface listWebhooksOperation {\n readonly path: \"/v1/partner/webhooks\";\n readonly method: \"GET\";\n request: never;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n webhooks?: Array<Webhook>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Create webhook subscription\n * POST /v1/partner/webhooks\n */\nexport interface createWebhookOperation {\n readonly path: \"/v1/partner/webhooks\";\n readonly method: \"POST\";\n request: {\n /** Events to subscribe to */\n events: Array<\"device.online\" | \"device.offline\" | \"device.requirements_updated\" | \"impression.recorded\" | \"creative.rewards_ready\" | \"campaign.allocated\" | \"payout.processed\" | \"sensing.threshold_crossed\" | \"audience.spike\" | \"audience.venue_busy\" | \"audience.purchase_intent\" | \"audience.demographics_update\" | \"venue.traffic_summary\" | \"programmatic.ad_started\" | \"programmatic.ad_ended\" | \"programmatic.no_fill\" | \"programmatic.error\" | \"screen_group.created\" | \"screen_group.updated\" | \"screen_group.deleted\" | \"screen_group.member.added\" | \"screen_group.member.removed\" | \"screen_group.content_policy.updated\" | \"screen_group.sensing_config.updated\" | \"fleet_command.completed\" | \"fleet_command.failed\">;\n /** Webhook endpoint URL (HTTPS required in production) */\n url: string;\n};\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n response: {\n data?: {\n events?: Array<string>;\n /** Webhook signing secret (shown only once!) */\n secret?: string;\n status?: \"active\" | \"inactive\";\n url?: string;\n webhook_id?: string;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Delete webhook\n * DELETE /v1/partner/webhooks/{webhookId}\n */\nexport interface deleteWebhookOperation {\n readonly path: \"/v1/partner/webhooks/{webhookId}\";\n readonly method: \"DELETE\";\n request: never;\n params: {\n webhookId: string;\n};\n query: Record<string, unknown>;\n response: {\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Update webhook\n * PATCH /v1/partner/webhooks/{webhookId}\n */\nexport interface updateWebhookOperation {\n readonly path: \"/v1/partner/webhooks/{webhookId}\";\n readonly method: \"PATCH\";\n request: {\n events?: Array<\"device.online\" | \"device.offline\" | \"device.requirements_updated\" | \"impression.recorded\" | \"creative.rewards_ready\" | \"campaign.allocated\" | \"payout.processed\" | \"sensing.threshold_crossed\" | \"audience.spike\" | \"audience.venue_busy\" | \"audience.purchase_intent\" | \"audience.demographics_update\" | \"venue.traffic_summary\" | \"programmatic.ad_started\" | \"programmatic.ad_ended\" | \"programmatic.no_fill\" | \"programmatic.error\" | \"screen_group.created\" | \"screen_group.updated\" | \"screen_group.deleted\" | \"screen_group.member.added\" | \"screen_group.member.removed\" | \"screen_group.content_policy.updated\" | \"screen_group.sensing_config.updated\" | \"fleet_command.completed\" | \"fleet_command.failed\">;\n status?: \"active\" | \"inactive\";\n url?: string;\n};\n params: {\n webhookId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: Webhook;\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Get delivery history\n * GET /v1/partner/webhooks/{webhookId}/deliveries\n */\nexport interface getWebhookDeliveriesOperation {\n readonly path: \"/v1/partner/webhooks/{webhookId}/deliveries\";\n readonly method: \"GET\";\n request: never;\n params: {\n webhookId: string;\n};\n query: {\n limit?: number;\n};\n response: {\n data?: {\n deliveries?: Array<WebhookDelivery>;\n };\n success?: boolean;\n};\n}\n\n/**\n * Test webhook\n * POST /v1/partner/webhooks/{webhookId}/test\n */\nexport interface testWebhookOperation {\n readonly path: \"/v1/partner/webhooks/{webhookId}/test\";\n readonly method: \"POST\";\n request: never;\n params: {\n webhookId: string;\n};\n query: Record<string, unknown>;\n response: {\n data?: {\n error?: string | null;\n response_time_ms?: number;\n status?: number;\n };\n message?: string;\n success?: boolean;\n};\n}\n\n/**\n * Dry-run validate a request body against an operation schema\n * POST /v1/playground/{operation_id}\n */\nexport interface playgroundDryRunOperation {\n readonly path: \"/v1/playground/{operation_id}\";\n readonly method: \"POST\";\n request: never;\n params: {\n /** The operationId of the endpoint to validate against. */\n operation_id: string;\n};\n query: Record<string, unknown>;\n response: PlaygroundResult;\n}\n\n/**\n * Look up metadata for a recent API request\n * GET /v1/requests/{request_id}\n */\nexport interface getRequestMetadataOperation {\n readonly path: \"/v1/requests/{request_id}\";\n readonly method: \"GET\";\n request: never;\n params: {\n /** Server-generated request ID to look up. */\n request_id: string;\n};\n query: Record<string, unknown>;\n response: RequestMetadataResponse;\n}\n\n\n// ─── Type registry ───────────────────────────────────────────────────────\n\nexport const generatedTypes = {\n schemaCount: 194,\n operationCount: 242,\n schemas: [\"Ad\",\"AdSettings\",\"AdvertisingIdentity\",\"AnalyticsData\",\"AudienceAnalyticsResponse\",\"AudienceAttentionPrediction\",\"AudienceData\",\"AudienceExportResponse\",\"AudienceLoyaltyFeaturesRow\",\"AudienceMetricsResponse\",\"AudienceSegment\",\"AudienceSocialAttention\",\"BatchResult\",\"BillingInfo\",\"BleScanResultData\",\"CanonicalError\",\"ChangelogEntry\",\"ChangelogResponse\",\"CompletedImpressions\",\"ContentFitSettings\",\"ContentPreferencesInput\",\"ContentPreferencesResponse\",\"ContentPreset\",\"CreatePartnerSensingRouteRequest\",\"CreativeClassification\",\"CreativeRewardsReadyEvent\",\"DashboardResponse\",\"DataApi_AdSightingRecord\",\"DataApi_AdSightingsHistoryResponse\",\"DataApi_AdSightingsResponse\",\"DataApi_AggregateResponse\",\"DataApi_Error\",\"DataApi_ErrorCode\",\"DataApi_ExampleHealthcheckResponse\",\"DataApi_FeedbackRewardQuery\",\"DataApi_LookalikeResponse\",\"DataApi_QueryDslBody\",\"DataApi_QueryDslResponse\",\"DataApi_QueryFilter\",\"DataApi_QueryOrderBy\",\"DataApi_ScreenAudienceResponse\",\"DataApi_ScreenHistoryResponse\",\"DataApi_ScreensListResponse\",\"DataApi_SemanticSearchResponse\",\"DataApi_StatsResponse\",\"DataApi_StreamResponse\",\"DataApi_UsageResponse\",\"DataExportSummaryResponse\",\"DemandPartners\",\"DemographicsResponse\",\"Device\",\"DeviceAuthRefusal\",\"DeviceCapabilityPayload\",\"DeviceSummary\",\"DisplaySettingsInput\",\"DisplaySettingsResponse\",\"EarnerScreens\",\"EarningsBreakdownResponse\",\"EarningsData\",\"EarningsResponse\",\"EarningsSources\",\"Error\",\"ErrorCode\",\"ErrorCodeResponse\",\"ErrorEntry\",\"ErrorEnvelope\",\"ErrorListItem\",\"ErrorListResponse\",\"ErrorType\",\"ExtendedIdentity\",\"FeedbackBrand\",\"FeedbackBrandRequest\",\"FeedbackBrandResponse\",\"FeedbackCreateRequest\",\"FeedbackCreativeReview\",\"FeedbackExperiment\",\"FeedbackExperimentDetail\",\"FeedbackExperimentDetailResponse\",\"FeedbackExperimentResponse\",\"FeedbackGenerateRequest\",\"FeedbackGenerateResponse\",\"FeedbackGeneratedCreative\",\"FeedbackImportRequest\",\"FeedbackPolicyRequest\",\"FeedbackRecoveredCreative\",\"FeedbackRegisterVariantsRequest\",\"FeedbackReviewRequest\",\"FeedbackReviewResponse\",\"FeedbackRewardQuery\",\"FeedbackRewardRow\",\"FeedbackRewards\",\"FeedbackRewardsResponse\",\"FeedbackStopRequest\",\"FeedbackVariant\",\"FleetCommand\",\"FleetCommandEvent\",\"HeaderBiddingSettings\",\"HttpProbeResult\",\"HybridError\",\"Impression\",\"LegacyCodedError\",\"LegacyError\",\"Mcp_create_media_buy_Args\",\"Mcp_discover_inventory_Args\",\"Mcp_get_media_buy_delivery_Args\",\"Mcp_get_media_buys_Args\",\"Mcp_get_products_Args\",\"Mcp_list_accounts_Args\",\"Mcp_sync_accounts_Args\",\"Mcp_sync_creatives_Args\",\"Mcp_update_media_buy_Args\",\"MdnsNetworkDevice\",\"MeasurementMethodology\",\"OpenrtbRevenueScreenHourly\",\"Pagination\",\"Partner\",\"PartnerAdDeliveryProfile\",\"PartnerCreativeWarnings\",\"PartnerDefaultStream\",\"PartnerDefaultStreamCreate\",\"PartnerDefaultStreamUpdate\",\"PartnerDeviceBatchRegistrationInput\",\"PartnerDeviceHeartbeatBody\",\"PartnerDevicePlay\",\"PartnerDeviceRegistrationInput\",\"PartnerHeartbeatCommand\",\"PartnerHeartbeatData\",\"PartnerHeartbeatMeasurementMetadata\",\"PartnerHeartbeatQueryData\",\"PartnerHeartbeatQueryResponse\",\"PartnerHeartbeatRequest\",\"PartnerHeartbeatResponse\",\"PartnerHeartbeatTelemetry\",\"PartnerInteraction\",\"PartnerLBarContent\",\"PartnerLBarDisplayPreferences\",\"PartnerLBarSettings\",\"PartnerLocationCapability\",\"PartnerLocationRail\",\"PartnerMonetizationRequirements\",\"PartnerNetworkConfig\",\"PartnerNetworkConfigUpdate\",\"PartnerPlay\",\"PartnerReconciliationReports\",\"PartnerRegistrationDisplaySettings\",\"PartnerRequirement\",\"PartnerSafeHeartbeatTelemetry\",\"PartnerSelfPromoAd\",\"PartnerSelfPromoCreate\",\"PartnerSelfPromoPlacement\",\"PartnerStats\",\"PartnerTripState\",\"PartnerVariants\",\"PartnerZoneCpmDaily\",\"PayoutResponse\",\"PlaygroundResult\",\"PlaygroundValidationError\",\"ProgrammaticSettingsInput\",\"ProgrammaticSettingsResponse\",\"RequestMetadataResponse\",\"ResolvedContentPolicy\",\"ResolvedSensingConfig\",\"RolePermissions\",\"SDKConfig\",\"Screen\",\"ScreenAnalyticsResponse\",\"ScreenGroup\",\"ScreenGroupContentPolicy\",\"ScreenGroupContentPolicyInput\",\"ScreenGroupEvent\",\"ScreenGroupSensingConfig\",\"ScreenGroupSensingConfigInput\",\"SensingPerformanceMode\",\"SensingRoute\",\"SensingRouteAction\",\"SensingRouteCondition\",\"SensingRouteReviewTimingMode\",\"SensingRouteReviewWaitSeconds\",\"SensingRouteScreenFocusRegion\",\"SensingSettingsResponse\",\"SensingThresholdCrossedEvent\",\"SsdpDeviceInfo\",\"TeamInviteResponse\",\"TeamMember\",\"Transaction\",\"TrillboardsError\",\"UpdatePartnerSensingRouteRequest\",\"VastWaterfall\",\"VastWaterfallSource\",\"Webhook\",\"WebhookDelivery\",\"WebhookEvent\",\"WifiEnvironmentSnapshot\",\"WifiScanResult\"] as const,\n operations: [\"queryChangelog\",\"dataApiAdSightings\",\"dataApiAdSightingsHistory\",\"dataApiAggregate\",\"dataApiLookalikeScreens\",\"dataApiQueryDsl\",\"dataApiListScreens\",\"dataApiScreenAudienceLive\",\"dataApiScreenAudienceHistory\",\"dataApiSemanticSearch\",\"dataApiStats\",\"dataApiStream\",\"dataApiUsage\",\"listErrorCodes\",\"getErrorCode\",\"exampleHealthcheck\",\"listPartnerSelfPromoAds\",\"createPartnerSelfPromoAd\",\"listPartnerSelfPromoPlacements\",\"schedulePartnerSelfPromo\",\"createPartnerSelfPromoUpload\",\"disableSelfPromoAd\",\"registerAgent\",\"getAnalytics\",\"getAudienceContinuity\",\"getAudienceProfile\",\"getBleSummary\",\"getContentPerformance\",\"searchCreativeCatalog\",\"getCreativeObservations\",\"getCreativeScenes\",\"getCrossScreenFlow\",\"getPartnerAnalyticsDashboard\",\"getDeviceSuiteSignature\",\"exportPartnerAnalytics\",\"exportPartnerAdsByDay\",\"getFleetHourly\",\"getHaloFunnel\",\"getLoyaltyClock\",\"getNetworkSummary\",\"getNetworkTrend\",\"getProximitySummary\",\"getRevenueAttentionCorrelation\",\"getSceneFeed\",\"getSceneIntelligence\",\"rotateApiKey\",\"getAudienceAnalytics\",\"exportAudienceData\",\"getAudienceHeatmap\",\"getLiveAudience\",\"getLookalikeScreens\",\"predictScreenAudience\",\"getResolvedHouseholdReach\",\"listAudienceSegments\",\"createAudienceSegment\",\"deleteAudienceSegment\",\"getAudienceSegment\",\"updateAudienceSegment\",\"matchAudienceSegment\",\"estimateSegmentReach\",\"generateSegmentVastTag\",\"getAudienceStats\",\"cancelBillingSubscription\",\"purchaseCredits\",\"createCreditCheckout\",\"listBillingPlans\",\"setupBilling\",\"createBillingSubscription\",\"getBillingSubscription\",\"getBillingUsage\",\"getBillingUsageSummary\",\"stripeWebhookCallback\",\"getConnectDashboard\",\"getConnectEarningsChart\",\"getConnectScreenHealth\",\"listContentPresets\",\"getContentPreset\",\"createCreativeFeedbackExperiment\",\"getCreativeFeedbackExperiment\",\"configureCreativeFeedbackBrand\",\"generateCreativeFeedbackCreative\",\"importCreativeFeedbackCreative\",\"reviewCreativeFeedbackCreative\",\"updateCreativeFeedbackPolicy\",\"getCreativeFeedbackRewards\",\"stopCreativeFeedbackExperiment\",\"registerCreativeFeedbackVariants\",\"submitPartnerCreative\",\"getPartnerCreative\",\"getDashboard\",\"exportProximityGraphEdges\",\"getDataExportSummary\",\"exportDataset\",\"getGeohashAudiencePanel\",\"getOutcomeAttribution\",\"registerDevice\",\"deleteDevice\",\"getDevice\",\"updateDevice\",\"getAdsForDevice\",\"ackDeviceCommand\",\"getDeviceHeartbeat\",\"heartbeat\",\"updateDeviceImpressionMultiplier\",\"recordPartnerDeviceInteraction\",\"recordPartnerDevicePlay\",\"recordProgrammaticEvent\",\"recordPartnerDeviceTripState\",\"getVastForDevice\",\"listDevices\",\"registerDeviceV2\",\"registerDevicesBatch\",\"batchUpdateDeviceOrientation\",\"reconcileDevices\",\"partnerDocsBrowser\",\"getEarnings\",\"getEarningsBreakdown\",\"requestPayout\",\"getTransactions\",\"getActiveSSEConnections\",\"subscribeToEvents\",\"getFleetCommand\",\"getFleetAnalytics\",\"pushFleetCommand\",\"getFleetOverview\",\"updateFleetSettings\",\"recordImpressionGet\",\"recordImpressionPost\",\"recordImpressionsBatch\",\"getPartnerInfo\",\"listIntentCategories\",\"getIntentCategory\",\"getIntentPricing\",\"getMeasurementMethodology\",\"getMeasurementMethodologyJson\",\"getNetworkConfig\",\"patchNetworkConfig\",\"updateNetworkConfig\",\"getNetworkTrends\",\"partnerPing\",\"partnerPingAuth\",\"recordPartnerPlay\",\"recordPartnerPlaysBatch\",\"getPricing\",\"recordProgrammaticEventLegacyAlias\",\"quickStart\",\"registerPartner\",\"generateSandboxEvent\",\"resetSandbox\",\"createSandboxScreen\",\"getSandboxStatus\",\"listScreenGroups\",\"createScreenGroup\",\"deleteScreenGroup\",\"getScreenGroup\",\"updateScreenGroup\",\"getScreenGroupAnalytics\",\"createScreenGroupCommand\",\"getScreenGroupContentPolicy\",\"updateScreenGroupContentPolicy\",\"removeScreenGroupMembers\",\"listScreenGroupMembers\",\"addScreenGroupMembers\",\"getScreenGroupSensingConfig\",\"updateScreenGroupSensingConfig\",\"getScreenAudienceSegments\",\"batchUpdateContentPreferences\",\"batchUpdateDisplaySettings\",\"batchUpdateProgrammaticSettings\",\"resolvePartnerScreen\",\"getScreenAnalytics\",\"getPartnerScreenAtmosphere\",\"getPartnerScreenIntelligence\",\"getPartnerScreenTasteProfile\",\"getPartnerScreenTraffic\",\"getPartnerScreenVAS\",\"applyContentPreset\",\"getAudienceMetrics\",\"getContentPreferences\",\"patchContentPreferences\",\"updateContentPreferences\",\"listPartnerDefaultStreams\",\"createPartnerDefaultStream\",\"createPartnerDefaultStreamImages\",\"reorderPartnerDefaultStreams\",\"createPartnerDefaultStreamUpload\",\"deletePartnerDefaultStream\",\"updatePartnerDefaultStream\",\"getScreenDemographics\",\"getDisplaySettings\",\"patchDisplaySettings\",\"updateDisplaySettings\",\"getPartnerLBarSettings\",\"updatePartnerLBarSettings\",\"updatePartnerLBarLiveUpdates\",\"getProgrammaticSettings\",\"patchProgrammaticSettings\",\"updateProgrammaticSettings\",\"getResolvedContentPolicy\",\"getResolvedSensingConfig\",\"getScreenSensingChips\",\"interpretScreenSensingIntent\",\"deployScreenSensingProfile\",\"getScreenSensingSettings\",\"updateScreenSensingSettings\",\"listPartnerSensingRoutes\",\"createPartnerSensingRoute\",\"deletePartnerSensingRoute\",\"getPartnerSensingRoute\",\"updatePartnerSensingRoute\",\"createStripeConnectAccount\",\"getStripeLoginLink\",\"getStripeOnboardingLink\",\"requestStripePayout\",\"getStripePayoutHistory\",\"getStripeAccountStatus\",\"handleStripeWebhook\",\"listTeamMembers\",\"acceptInvitation\",\"inviteTeamMember\",\"getInvitationByToken\",\"resendInvitation\",\"removeTeamMember\",\"changeTeamMemberRole\",\"getTrackingPublicKey\",\"recordTrackingBatch\",\"getUsageSummary\",\"getUsageDashboard\",\"getRateLimitStatus\",\"getVastConfig\",\"getVenuesNearby\",\"getVenueAtmosphere\",\"getVenueTraffic\",\"getVenueIntelligence\",\"listWebhooks\",\"createWebhook\",\"deleteWebhook\",\"updateWebhook\",\"getWebhookDeliveries\",\"testWebhook\",\"playgroundDryRun\",\"getRequestMetadata\"] as const,\n} as const;\n\nexport type GeneratedSchemaName = (typeof generatedTypes.schemas)[number];\nexport type GeneratedOperationName = (typeof generatedTypes.operations)[number];\n"]}
|